[
  {
    "path": ".editorconfig",
    "content": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\nend_of_line = lf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: Report a bug in Marathon Recompiled\ntype: \"Bug\"\nbody:\n  - type: checkboxes\n    id: validation\n    attributes:\n      label: Validation\n      options:\n        - label: I have checked the [Issues](https://github.com/sonicnext-dev/MarathonRecomp/issues) page to see if my problem has already been reported\n          required: true\n        - label: I have confirmed that this bug does not occur in the original game running on original Xbox 360 hardware\n          required: true\n  - type: checkboxes\n    id: dlc\n    attributes:\n      label: DLC\n      description: If you have DLC installed, please specify which ones you have.\n      options:\n        - label: Additional Episode \"Sonic Boss Attack\"\n        - label: Additional Episode \"Shadow Boss Attack\"\n        - label: Additional Episode \"Silver Boss Attack\"\n        - label: Additional Episode \"Team Attack Amigo\"\n        - label: Additional Mission Pack \"Sonic/Very Hard\"\n        - label: Additional Mission Pack \"Shadow/Very Hard\"\n        - label: Additional Mission Pack \"Silver/Very Hard\"\n  - type: textarea\n    id: mods\n    attributes:\n      label: Mods\n      description: Provide a list of your enabled mods in Mod Manager here. You will not receive support for issues *caused* by mods.\n  - type: textarea\n    id: codes\n    attributes:\n      label: Codes\n      description: Provide a list of your enabled codes in Mod Manager here.\n  - type: textarea\n    id: description\n    attributes:\n      label: Describe the Bug\n      description: A clear and concise description of what the bug is.\n    validations:\n      required: true\n  - type: textarea\n    id: reproduction\n    attributes:\n      label: Steps to Reproduce\n      description: Provide steps to reproduce the bug\n      placeholder: |\n        1. Go to '...'\n        2. etc.\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: Expected Behavior\n      description: A clear and concise description of what you expected to happen.\n    validations:\n      required: true\n  - type: textarea\n    id: footage\n    attributes:\n      label: Footage\n      description: Attach a screenshot or video of the bug. If possible, please also provide footage of the expected behaviour on original Xbox 360 hardware.\n  - type: textarea\n    id: specifications\n    attributes:\n      label: Specifications\n      description: Fill out the following details\n      value: |\n        - CPU: (e.g. Intel Core [...], AMD Ryzen [...], etc.)\n        - GPU: (e.g. NVIDIA GeForce [...], Radeon HD [...], Intel HD [...], etc.)\n        - GPU Driver: (e.g NVIDIA driver 545.XX, AMD driver 24.X.X, etc.)\n        - OS: (e.g. Windows 10, Windows 11, Linux distro)\n        - Version: (e.g. 1.0.0)\n    validations:\n      required: true\n  - type: textarea\n    id: context\n    attributes:\n      label: Additional Context\n      description: Provide any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/dev_report.yml",
    "content": "name: Dev Report\ndescription: For developer use only!\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/workflows/validate-external.yml",
    "content": "name: validate-external\non:\n  pull_request_target:\n    types: [opened, synchronize]\njobs:\n  authorize:\n    if: github.repository != github.event.pull_request.head.repo.full_name\n    environment:\n      ${{ github.event_name == 'pull_request_target' &&\n      github.event.pull_request.head.repo.full_name != github.repository &&\n      'external' || 'internal' }}\n    runs-on: ubuntu-24.04\n    steps:\n      - run: echo ✓\n  build:\n    needs: authorize\n    uses: ./.github/workflows/validate.yml\n    secrets:\n      ASSET_REPO: ${{ secrets.ASSET_REPO }}\n      ASSET_REPO_TOKEN: ${{ secrets.ASSET_REPO_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/validate-internal.yml",
    "content": "name: validate-internal\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    types: [opened, synchronize]\njobs:\n  build:\n    if: github.event_name == 'push' || github.repository == github.event.pull_request.head.repo.full_name\n    uses: ./.github/workflows/validate.yml\n    secrets: inherit\n"
  },
  {
    "path": ".github/workflows/validate.yml",
    "content": "name: validate\non:\n  workflow_call:\n    secrets:\n      ASSET_REPO:\n        required: true\n      ASSET_REPO_TOKEN:\n        required: true\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n  \njobs:\n  build-linux:\n    name: Build Linux\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        preset: [\"linux-debug\", \"linux-release\", \"linux-relwithdebinfo\"]\n    env:\n      LLVM_VERSION: 18\n      CMAKE_PRESET: ${{ matrix.preset }}\n\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Checkout Private Repository\n        uses: actions/checkout@v4\n        with:\n          repository: ${{ secrets.ASSET_REPO }}\n          token: ${{ secrets.ASSET_REPO_TOKEN }}\n          path: ./private\n\n      - name: Setup ccache\n        uses: hendrikmuhs/ccache-action@v1.2\n        with:\n          key: ccache-${{ runner.os }}-${{ matrix.preset }}\n  \n      - name: Cache vcpkg\n        uses: actions/cache@v4\n        with:\n          path: |\n            ./thirdparty/vcpkg/downloads\n            ./thirdparty/vcpkg/packages\n          key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}\n          restore-keys: |\n              vcpkg-${{ runner.os }}-\n\n      - name: Install Dependencies (Linux)\n        run: |-\n          sudo apt update\n          sudo apt install -y ninja-build llvm-${{ env.LLVM_VERSION }}-dev libgtk-3-dev libasound2-dev libpulse-dev libpipewire-0.3-dev\n\n      - name: Cache ccache Directory\n        uses: actions/cache@v4\n        with:\n          path: /tmp/ccache\n          key: ccache-${{ runner.os }}-${{ matrix.preset }}\n\n      - name: Prepare Project\n        run: cp ./private/* ./MarathonRecompLib/private\n  \n      - name: Configure Project\n        env:\n          CCACHE_DIR: /tmp/ccache\n        run: cmake . --preset ${{ env.CMAKE_PRESET }} -DSDL2MIXER_VORBIS=VORBISFILE -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache\n\n      - name: Build Project\n        env:\n          CCACHE_DIR: /tmp/ccache\n        run: cmake --build ./out/build/${{ env.CMAKE_PRESET }} --target MarathonRecomp\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: MarathonRecomp-Linux-${{ env.CMAKE_PRESET }}\n          path: ./out/build/${{ env.CMAKE_PRESET }}/MarathonRecomp/MarathonRecomp\n  build-windows:\n    name: Build Windows\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        preset: [\"x64-Clang-Debug\", \"x64-Clang-Release\", \"x64-Clang-RelWithDebInfo\"]\n    env:\n      CMAKE_PRESET: ${{ matrix.preset }}\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Checkout private repository\n        uses: actions/checkout@v4\n        with:\n          repository: ${{ secrets.ASSET_REPO }}\n          token: ${{ secrets.ASSET_REPO_TOKEN }}\n          path: .\\private\n\n      - name: Setup ccache\n        uses: hendrikmuhs/ccache-action@v1.2\n        with:\n          key: ccache-${{ runner.os }}-${{ matrix.preset }}\n\n      - name: Cache vcpkg\n        uses: actions/cache@v4\n        with:\n          path: |\n            ./thirdparty/vcpkg/downloads\n            ./thirdparty/vcpkg/packages\n          key: vcpkg-${{ runner.os }}-${{ hashFiles('vcpkg.json') }}\n          restore-keys: |\n              vcpkg-${{ runner.os }}-\n\n      - name: Install dependencies\n        run: |\n          choco install ninja\n          Remove-Item -Path \"C:\\ProgramData\\Chocolatey\\bin\\ccache.exe\" -Force -ErrorAction SilentlyContinue\n      \n      - name: Configure Developer Command Prompt\n        uses: ilammy/msvc-dev-cmd@v1\n\n      - name: Prepare Project\n        run: |\n          $commitMessage = git log -1 --pretty=%s\n          Add-Content -Path $env:GITHUB_ENV -Value \"commit_message=$commitMessage\"\n          copy .\\private\\* .\\MarathonRecompLib\\private\n\n      - name: Configure Project\n        run: cmake . --preset ${{ env.CMAKE_PRESET }} -DSDL2MIXER_VORBIS=VORBISFILE -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache\n\n      - name: Build Project\n        run: cmake --build .\\out\\build\\${{ env.CMAKE_PRESET }} --target MarathonRecomp\n      \n      - name: Pack Release\n        run: |\n          New-Item -ItemType Directory -Path .\\release\n          New-Item -ItemType Directory -Path .\\release\\D3D12\n\n          Move-Item -Path \".\\out\\build\\${{ env.CMAKE_PRESET }}\\MarathonRecomp\\D3D12\\D3D12Core.dll\" -Destination \".\\release\\D3D12\\D3D12Core.dll\"\n          Move-Item -Path \".\\out\\build\\${{ env.CMAKE_PRESET }}\\MarathonRecomp\\dxcompiler.dll\" -Destination \".\\release\\dxcompiler.dll\"\n          Move-Item -Path \".\\out\\build\\${{ env.CMAKE_PRESET }}\\MarathonRecomp\\dxil.dll\" -Destination \".\\release\\dxil.dll\"\n          Move-Item -Path \".\\out\\build\\${{ env.CMAKE_PRESET }}\\MarathonRecomp\\MarathonRecomp.exe\" -Destination \".\\release\\MarathonRecomp.exe\"\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: MarathonRecomp-Windows-${{ env.CMAKE_PRESET }}\n          path: .\\release\n\n      - name: Upload PDB\n        uses: actions/upload-artifact@v4\n        if: ${{ matrix.preset != 'x64-Clang-Release' }}\n        with:\n          name: MarathonRecomp-Windows-${{ env.CMAKE_PRESET }}-PDB\n          path: .\\out\\build\\${{ env.CMAKE_PRESET }}\\MarathonRecomp\\MarathonRecomp.pdb\n  build-flatpak:\n    name: Build Flatpak\n    runs-on: ubuntu-24.04\n    env:\n      FLATPAK_ID: io.github.sonicnext_dev.marathonrecomp\n      FREEDESKTOP_VERSION: 23.08\n      LLVM_VERSION: 18\n\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Checkout Private Repository\n        uses: actions/checkout@v4\n        with:\n          repository: ${{ secrets.ASSET_REPO }}\n          token: ${{ secrets.ASSET_REPO_TOKEN }}\n          path: ./private\n\n      - name: Install Dependencies\n        run: |-\n          sudo apt update\n          sudo apt install -y flatpak-builder ccache\n\n      - name: Setup ccache\n        uses: actions/cache@v4\n        with:\n          path: /tmp/ccache\n          key: ccache-${{ runner.os }}\n\n      - name: Prepare Project\n        run: cp ./private/* ./MarathonRecompLib/private  \n\n      - name: Prepare Flatpak\n        run: |\n          flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo\n          flatpak --user install -y flathub org.freedesktop.Sdk//${{ env.FREEDESKTOP_VERSION }}\n          flatpak --user install -y flathub org.freedesktop.Sdk.Extension.llvm${{ env.LLVM_VERSION }}//${{ env.FREEDESKTOP_VERSION }}\n\n      - name: Build Flatpak\n        run: |\n          echo \"commit_message=$(git log -1 --pretty=%s)\" >> $GITHUB_ENV\n          export CCACHE_DIR=/tmp/ccache\n          flatpak-builder --user --force-clean --install-deps-from=flathub --repo=repo --ccache builddir ./flatpak/${{ env.FLATPAK_ID }}.json\n          flatpak build-bundle repo ./${{ env.FLATPAK_ID }}.flatpak ${{ env.FLATPAK_ID }} --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: MarathonRecomp-Flatpak\n          path: ./${{ env.FLATPAK_ID }}.flatpak\n  build-macos:\n    name: Build macOS\n    runs-on: macos-15\n    strategy:\n      matrix:\n        arch: [ \"arm64\" ]\n        preset: [\"macos-debug\", \"macos-release\", \"macos-relwithdebinfo\"]\n    env:\n      CMAKE_PRESET: ${{ matrix.preset }}\n\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Checkout Private Repository\n        uses: actions/checkout@v4\n        with:\n          repository: ${{ secrets.ASSET_REPO }}\n          token: ${{ secrets.ASSET_REPO_TOKEN }}\n          path: ./private\n\n      - name: Setup ccache\n        uses: hendrikmuhs/ccache-action@v1.2\n        with:\n          key: ccache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.preset }}\n\n      - name: Cache vcpkg\n        uses: actions/cache@v4\n        with:\n          path: |\n            ./thirdparty/vcpkg/downloads\n            ./thirdparty/vcpkg/packages\n          key: vcpkg-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('vcpkg.json') }}\n          restore-keys: |\n            vcpkg-${{ runner.os }}-${{ matrix.arch }}-\n\n      - name: Install Dependencies (macOS)\n        run: |\n          brew install ninja\n\n      - name: Cache ccache Directory\n        uses: actions/cache@v4\n        with:\n          path: /tmp/ccache\n          key: ccache-${{ runner.os }}-${{ matrix.arch }}-${{ matrix.preset }}\n\n      - name: Prepare Project\n        run: |\n          cp ./private/* ./MarathonRecompLib/private\n\n      - name: Configure Project\n        env:\n          CCACHE_DIR: /tmp/ccache\n        run: cmake . --preset ${{ env.CMAKE_PRESET }} -DCMAKE_OSX_ARCHITECTURES=${{ matrix.arch }} -DSDL2MIXER_VORBIS=VORBISFILE -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache\n\n      - name: Build Project\n        env:\n          CCACHE_DIR: /tmp/ccache\n        run: cmake --build ./out/build/${{ env.CMAKE_PRESET }} --target MarathonRecomp\n\n      - name: Pack Release\n        run: |\n          codesign --deep -fs - \"./out/build/${{ env.CMAKE_PRESET }}/MarathonRecomp/Marathon Recompiled.app\"\n          tar -czf MarathonRecomp-macOS-${{ matrix.arch }}-${{ env.CMAKE_PRESET }}.tar.gz -C ./out/build/${{ env.CMAKE_PRESET }}/MarathonRecomp \"Marathon Recompiled.app\"\n\n      - name: Upload Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: MarathonRecomp-macOS-${{ matrix.arch }}-${{ env.CMAKE_PRESET }}\n          path: MarathonRecomp-macOS-${{ matrix.arch }}-${{ env.CMAKE_PRESET }}.tar.gz\n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n[Oo]ut/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\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# Visual Studio Trace Files\n*.e2e\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# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\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# Note: 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# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable 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*.appx\n*.appxbundle\n*.appxupload\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\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\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\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\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\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio 6 auto-generated project file (contains which files were open etc.)\n*.vbp\n\n# Visual Studio 6 workspace and project file (working project files containing files to include in project)\n*.dsw\n*.dsp\n\n# Visual Studio 6 technical files\n*.ncb\n*.aps\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# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# Visual Studio History (VSHistory) files\n.vshistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n*.sln.iml\n.idea\n\n# macOS metadata files\n.DS_Store\n\nUnleashedRecompLib/build\nUnleashedRecompLib/ppc*\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"tools/XenonRecomp\"]\n\tpath = tools/XenonRecomp\n\turl = https://github.com/sonicnext-dev/XenonRecomp.git\n[submodule \"thirdparty/ddspp\"]\n\tpath = thirdparty/ddspp\n\turl = https://github.com/redorav/ddspp.git\n[submodule \"tools/XenosRecomp\"]\n\tpath = tools/XenosRecomp\n\turl = https://github.com/sonicnext-dev/XenosRecomp.git\n[submodule \"thirdparty/msdf-atlas-gen\"]\n\tpath = thirdparty/msdf-atlas-gen\n\turl = https://github.com/Chlumsky/msdf-atlas-gen.git\n[submodule \"thirdparty/vcpkg\"]\n\tpath = thirdparty/vcpkg\n\turl = https://github.com/microsoft/vcpkg\n[submodule \"thirdparty/SDL\"]\n\tpath = thirdparty/SDL\n\turl = https://github.com/libsdl-org/SDL.git\n[submodule \"thirdparty/stb\"]\n\tpath = thirdparty/stb\n\turl = https://github.com/nothings/stb.git\n[submodule \"thirdparty/concurrentqueue\"]\n\tpath = thirdparty/concurrentqueue\n\turl = https://github.com/cameron314/concurrentqueue.git\n[submodule \"thirdparty/magic_enum\"]\n\tpath = thirdparty/magic_enum\n\turl = https://github.com/Neargye/magic_enum.git\n[submodule \"thirdparty/nativefiledialog-extended\"]\n\tpath = thirdparty/nativefiledialog-extended\n\turl = https://github.com/btzy/nativefiledialog-extended.git\n[submodule \"thirdparty/imgui\"]\n\tpath = thirdparty/imgui\n\turl = https://github.com/ocornut/imgui.git\n[submodule \"thirdparty/unordered_dense\"]\n\tpath = thirdparty/unordered_dense\n\turl = https://github.com/martinus/unordered_dense.git\n[submodule \"thirdparty/SDL_mixer\"]\n\tpath = thirdparty/SDL_mixer\n\turl = https://github.com/libsdl-org/SDL_mixer\n[submodule \"thirdparty/implot\"]\n\tpath = thirdparty/implot\n\turl = https://github.com/epezent/implot.git\n[submodule \"thirdparty/json\"]\n\tpath = thirdparty/json\n\turl = https://github.com/nlohmann/json\n[submodule \"MarathonRecompResources\"]\n\tpath = MarathonRecompResources\n\turl = https://github.com/sonicnext-dev/MarathonRecompResources\n[submodule \"thirdparty/MoltenVK/MoltenVK\"]\n\tpath = thirdparty/MoltenVK/MoltenVK\n\turl = https://github.com/KhronosGroup/MoltenVK.git\n[submodule \"thirdparty/MoltenVK/SPIRV-Cross\"]\n\tpath = thirdparty/MoltenVK/SPIRV-Cross\n\turl = https://github.com/KhronosGroup/SPIRV-Cross.git\n[submodule \"thirdparty/plume\"]\n\tpath = thirdparty/plume\n\turl = https://github.com/renderbag/plume\n[submodule \"thirdparty/ffmpeg-core\"]\n\tpath = thirdparty/ffmpeg-core\n\turl = https://github.com/sonicnext-dev/ffmpeg-core\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required (VERSION 3.20)\n\nif(NOT DEFINED ENV{VCPKG_ROOT})\n    message(FATAL_ERROR \"VCPKG_ROOT is not defined!\")\nendif()\n\nset(MARATHON_RECOMP_THIRDPARTY_ROOT ${CMAKE_SOURCE_DIR}/thirdparty)\nset(MARATHON_RECOMP_TOOLS_ROOT ${CMAKE_SOURCE_DIR}/tools)\nset(CMAKE_CXX_STANDARD 20)\nset(BUILD_SHARED_LIBS OFF)\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\nset(CMAKE_MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\n\nproject(\"MarathonRecomp-ALL\")\n\nif (APPLE)\n    enable_language(OBJC OBJCXX)\nendif()\n\nif (CMAKE_OSX_ARCHITECTURES)\n    set(MARATHON_RECOMP_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES})\nelseif(CMAKE_SYSTEM_PROCESSOR)\n    set(MARATHON_RECOMP_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})\nelse()\n    set(MARATHON_RECOMP_ARCHITECTURE ${CMAKE_HOST_SYSTEM_PROCESSOR})\nendif()\nstring(TOLOWER \"${MARATHON_RECOMP_ARCHITECTURE}\" MARATHON_RECOMP_ARCHITECTURE)\nmessage(STATUS \"Detected architecture: ${MARATHON_RECOMP_ARCHITECTURE}\")\n\nif (MARATHON_RECOMP_ARCHITECTURE STREQUAL \"x86_64\" OR MARATHON_RECOMP_ARCHITECTURE STREQUAL \"amd64\")\n    # Target Sandy Bridge for all projects\n    add_compile_options(\n        -march=sandybridge\n    )\nendif()\n\nif (CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n    # Normally only defined by Visual Studio, added for consistency\n    add_compile_definitions(_DEBUG)\nendif()\n\nadd_subdirectory(${MARATHON_RECOMP_THIRDPARTY_ROOT})\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT})\n\n# Include sub-projects.\nadd_subdirectory(\"MarathonRecompLib\")\nadd_subdirectory(\"MarathonRecomp\")\n"
  },
  {
    "path": "CMakePresets.json",
    "content": "{\n    \"version\": 8,\n    \"configurePresets\": [\n        {\n            \"name\": \"windows-base\",\n            \"hidden\": true,\n            \"generator\": \"Ninja\",\n            \"binaryDir\": \"${sourceDir}/out/build/${presetName}\",\n            \"installDir\": \"${sourceDir}/out/install/${presetName}\",\n            \"cacheVariables\": {\n                \"CMAKE_C_COMPILER\": \"clang-cl.exe\",\n                \"CMAKE_CXX_COMPILER\": \"clang-cl.exe\",\n                \"CMAKE_LINKER\": \"lld-link.exe\",\n                \"CMAKE_TOOLCHAIN_FILE\": {\n                    \"value\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\",\n                    \"type\": \"FILEPATH\"\n                }\n            },\n            \"environment\": {\n                \"VCPKG_ROOT\": \"${sourceDir}/thirdparty/vcpkg\"\n            },\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Windows\"\n            }\n        },\n        {\n            \"name\": \"x64-Clang-Debug\",\n            \"displayName\": \"Debug\",\n            \"inherits\": \"windows-base\",\n            \"architecture\": {\n                \"value\": \"x64\",\n                \"strategy\": \"external\"\n            },\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Debug\",\n                \"VCPKG_TARGET_TRIPLET\": {\n                    \"value\": \"x64-windows-static\",\n                    \"type\": \"STRING\"\n                }\n            }\n        },\n        {\n            \"name\": \"x64-Clang-RelWithDebInfo\",\n            \"displayName\": \"RelWithDebInfo\",\n            \"inherits\": \"x64-Clang-Debug\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"RelWithDebInfo\"\n            }\n        },\n        {\n            \"name\": \"x64-Clang-Release\",\n            \"displayName\": \"Release\",\n            \"inherits\": \"x64-Clang-Debug\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Release\",\n                \"CMAKE_INTERPROCEDURAL_OPTIMIZATION\": true\n            }\n        },\n        {\n            \"name\": \"linux-base\",\n            \"hidden\": true,\n            \"generator\": \"Ninja\",\n            \"binaryDir\": \"${sourceDir}/out/build/${presetName}\",\n            \"installDir\": \"${sourceDir}/out/install/${presetName}\",\n            \"cacheVariables\": {\n                \"CMAKE_TOOLCHAIN_FILE\": {\n                    \"value\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\",\n                    \"type\": \"FILEPATH\"\n                },\n                \"VCPKG_TARGET_TRIPLET\": {\n                    \"value\": \"x64-linux\",\n                    \"type\": \"STRING\"\n                },\n                \"VCPKG_CHAINLOAD_TOOLCHAIN_FILE\": \"${sourceDir}/toolchains/linux-clang.cmake\"\n            },\n            \"environment\": {\n                \"VCPKG_ROOT\": \"${sourceDir}/thirdparty/vcpkg\"\n            },\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Linux\"\n            },\n            \"vendor\": {\n                \"microsoft.com/VisualStudioRemoteSettings/CMake/2.0\": {\n                    \"remoteSourceRootDir\": \"$env{HOME}/.vs/$ms{projectDirName}\"\n                }\n            }\n        },\n        {\n            \"name\": \"linux-debug\",\n            \"displayName\": \"Linux-Debug\",\n            \"inherits\": \"linux-base\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Debug\"\n            }\n        },\n        {\n            \"name\": \"linux-relwithdebinfo\",\n            \"displayName\": \"Linux-RelWithDebInfo\",\n            \"inherits\": \"linux-base\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"RelWithDebInfo\"\n            }\n        },\n        {\n            \"name\": \"linux-release\",\n            \"displayName\": \"Linux-Release\",\n            \"inherits\": \"linux-base\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Release\",\n                \"CMAKE_INTERPROCEDURAL_OPTIMIZATION\": true\n            }\n        },\n        {\n            \"name\": \"macos-base\",\n            \"hidden\": true,\n            \"generator\": \"Ninja\",\n            \"binaryDir\": \"${sourceDir}/out/build/${presetName}\",\n            \"installDir\": \"${sourceDir}/out/install/${presetName}\",\n            \"cacheVariables\": {\n                \"CMAKE_TOOLCHAIN_FILE\": {\n                    \"value\": \"$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake\",\n                    \"type\": \"FILEPATH\"\n                },\n                \"CMAKE_OSX_DEPLOYMENT_TARGET\": \"13.0\"\n            },\n            \"environment\": {\n                \"VCPKG_ROOT\": \"${sourceDir}/thirdparty/vcpkg\"\n            },\n            \"condition\": {\n                \"type\": \"equals\",\n                \"lhs\": \"${hostSystemName}\",\n                \"rhs\": \"Darwin\"\n            },\n            \"vendor\": {\n                \"microsoft.com/VisualStudioRemoteSettings/CMake/2.0\": {\n                    \"remoteSourceRootDir\": \"$env{HOME}/.vs/$ms{projectDirName}\"\n                }\n            }\n        },\n        {\n            \"name\": \"macos-debug\",\n            \"displayName\": \"macOS-Debug\",\n            \"inherits\": \"macos-base\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Debug\"\n            }\n        },\n        {\n            \"name\": \"macos-relwithdebinfo\",\n            \"displayName\": \"macOS-RelWithDebInfo\",\n            \"inherits\": \"macos-base\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"RelWithDebInfo\"\n            }\n        },\n        {\n            \"name\": \"macos-release\",\n            \"displayName\": \"macOS-Release\",\n            \"inherits\": \"macos-base\",\n            \"cacheVariables\": {\n                \"CMAKE_BUILD_TYPE\": \"Release\",\n                \"CMAKE_INTERPROCEDURAL_OPTIMIZATION\": true\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "MarathonRecomp/.gitignore",
    "content": "/version.h\n/version.cpp"
  },
  {
    "path": "MarathonRecomp/CMakeLists.txt",
    "content": "project(\"MarathonRecomp\")\r\n\r\nif (WIN32)\r\n    option(MARATHON_RECOMP_D3D12 \"Add D3D12 support for rendering\" ON)\r\nendif()\r\n\r\nif (APPLE)\r\n    option(MARATHON_RECOMP_METAL \"Add Metal support for rendering\" ON)\r\nendif()\r\n\r\nif (CMAKE_SYSTEM_NAME MATCHES \"Linux\")\r\n    option(MARATHON_RECOMP_FLATPAK \"Configure the build for Flatpak compatibility.\" OFF)\r\nendif()\r\n\r\nfunction(BIN2C)\r\n    cmake_parse_arguments(BIN2C_ARGS \"\" \"TARGET_OBJ;SOURCE_FILE;DEST_FILE;ARRAY_NAME;COMPRESSION_TYPE\" \"\" ${ARGN})\r\n\r\n    if(NOT BIN2C_ARGS_TARGET_OBJ)\r\n        message(FATAL_ERROR \"TARGET_OBJ not specified.\")\r\n    endif()\r\n\r\n    if(NOT BIN2C_ARGS_SOURCE_FILE)\r\n        message(FATAL_ERROR \"SOURCE_FILE not specified.\")\r\n    endif()\r\n\r\n    if(NOT BIN2C_ARGS_DEST_FILE)\r\n        set(BIN2C_ARGS_DEST_FILE \"${BIN2C_ARGS_SOURCE_FILE}\")\r\n    endif()\r\n\r\n    if(NOT BIN2C_ARGS_COMPRESSION_TYPE)\r\n        set(BIN2C_ARGS_COMPRESSION_TYPE \"none\")\r\n    endif()\r\n\r\n    add_custom_command(OUTPUT \"${BIN2C_ARGS_DEST_FILE}.c\"\r\n        COMMAND $<TARGET_FILE:file_to_c> \"${BIN2C_ARGS_SOURCE_FILE}\" \"${BIN2C_ARGS_ARRAY_NAME}\" \"${BIN2C_ARGS_COMPRESSION_TYPE}\" \"${BIN2C_ARGS_DEST_FILE}.c\" \"${BIN2C_ARGS_DEST_FILE}.h\"\r\n        DEPENDS \"${BIN2C_ARGS_SOURCE_FILE}\"\r\n        BYPRODUCTS \"${BIN2C_ARGS_DEST_FILE}.h\"\r\n        COMMENT \"Generating binary header for ${BIN2C_ARGS_SOURCE_FILE}...\"\r\n    )\r\n\r\n    set_source_files_properties(${BIN2C_ARGS_DEST_FILE}.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON)\r\n    target_sources(${BIN2C_ARGS_TARGET_OBJ} PRIVATE ${BIN2C_ARGS_DEST_FILE}.c)\r\nendfunction()\r\n\r\nadd_compile_options(\r\n    -fno-strict-aliasing\r\n    -Wno-switch\r\n    -Wno-unused-function\r\n    -Wno-unused-variable\r\n    -Wno-unused-but-set-variable\r\n    -Wno-void-pointer-to-int-cast\r\n    -Wno-int-to-void-pointer-cast\r\n    -Wno-invalid-offsetof\r\n    -Wno-null-arithmetic\r\n    -Wno-null-conversion\r\n    -Wno-tautological-undefined-compare\r\n)\r\n\r\nif (WIN32)\r\n    add_compile_options(/fp:strict)\r\nelse()\r\n    add_compile_options(-ffp-model=strict)\r\nendif()\r\n\r\nadd_compile_definitions(\r\n    SDL_MAIN_HANDLED\r\n    _DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR # Microsoft wtf?\r\n    _CRT_SECURE_NO_WARNINGS)\r\n\r\nset(MARATHON_RECOMP_PRECOMPILED_HEADERS\r\n    \"stdafx.h\"\r\n)\r\n\r\nset(MARATHON_RECOMP_KERNEL_CXX_SOURCES\r\n    \"kernel/imports.cpp\"\r\n    \"kernel/xdm.cpp\"\r\n    \"kernel/heap.cpp\"\r\n    \"kernel/memory.cpp\"\r\n    \"kernel/xam.cpp\"\r\n    \"kernel/io/file_system.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_LOCALE_CXX_SOURCES\r\n    \"locale/achievement_locale.cpp\"\r\n    \"locale/config_locale.cpp\"\r\n    \"locale/locale.cpp\"\r\n)\r\n\r\nif (WIN32)\r\n    set(MARATHON_RECOMP_OS_CXX_SOURCES\r\n        \"os/win32/logger_win32.cpp\"\r\n        \"os/win32/media_win32.cpp\"\r\n        \"os/win32/process_win32.cpp\"\r\n        \"os/win32/user_win32.cpp\"\r\n        \"os/win32/version_win32.cpp\"\r\n    )\r\nelseif (CMAKE_SYSTEM_NAME MATCHES \"Linux\")\r\n    set(MARATHON_RECOMP_OS_CXX_SOURCES\r\n        \"os/linux/logger_linux.cpp\"\r\n        \"os/linux/media_linux.cpp\"\r\n        \"os/linux/process_linux.cpp\"\r\n        \"os/linux/user_linux.cpp\"\r\n        \"os/linux/version_linux.cpp\"\r\n    )\r\nelseif (APPLE)\r\n    set(MARATHON_RECOMP_OS_CXX_SOURCES\r\n        \"os/macos/logger_macos.cpp\"\r\n        \"os/macos/media_macos.cpp\"\r\n        \"os/macos/process_macos.cpp\"\r\n        \"os/macos/user_macos.cpp\"\r\n        \"os/macos/version_macos.cpp\"\r\n    )\r\nendif()\r\n\r\nset(MARATHON_RECOMP_CPU_CXX_SOURCES\r\n    \"cpu/guest_thread.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_GPU_CXX_SOURCES\r\n    \"gpu/video.cpp\"\r\n    \"gpu/imgui/imgui_common.cpp\"\r\n    \"gpu/imgui/imgui_font_builder.cpp\"\r\n    \"gpu/imgui/imgui_snapshot.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_APU_CXX_SOURCES\r\n    \"apu/audio.cpp\"\r\n    \"apu/xma_decoder.cpp\"\r\n    \"apu/embedded_player.cpp\"\r\n    \"apu/driver/sdl2_driver.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_HID_CXX_SOURCES\r\n    \"hid/hid.cpp\"\r\n    \"hid/driver/sdl_hid.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_PATCHES_CXX_SOURCES\r\n    \"patches/aspect_ratio_patches.cpp\"\r\n    \"patches/audio_patches.cpp\"\r\n    \"patches/camera_patches.cpp\"\r\n    \"patches/fps_patches.cpp\"\r\n    \"patches/frontend_listener.cpp\"\r\n    \"patches/input_patches.cpp\"\r\n    \"patches/loading_patches.cpp\"\r\n    \"patches/MainMenuTask_patches.cpp\"\r\n    \"patches/misc_patches.cpp\"\r\n    \"patches/pause_patches.cpp\"\r\n    \"patches/player_patches.cpp\"\r\n    \"patches/SaveDataTask_patches.cpp\"\r\n    \"patches/text_patches.cpp\"\r\n    \"patches/TitleTask_patches.cpp\"\r\n    \"patches/video_patches.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_UI_CXX_SOURCES\r\n    \"ui/achievement_menu.cpp\"\r\n    \"ui/achievement_overlay.cpp\"\r\n    \"ui/black_bar.cpp\"\r\n    \"ui/button_window.cpp\"\r\n    \"ui/common_menu.cpp\"\r\n    \"ui/fader.cpp\"\r\n    \"ui/game_window.cpp\"\r\n    \"ui/imgui_utils.cpp\"\r\n    \"ui/installer_wizard.cpp\"\r\n    \"ui/message_window.cpp\"\r\n    \"ui/options_menu.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_INSTALL_CXX_SOURCES\r\n    \"install/installer.cpp\"\r\n    \"install/iso_file_system.cpp\"\r\n    \"install/update_checker.cpp\"\r\n    \"install/xcontent_file_system.cpp\"\r\n    \"install/hashes/game.cpp\"\r\n    \"install/hashes/episode_sonic.cpp\"\r\n    \"install/hashes/episode_shadow.cpp\"\r\n    \"install/hashes/episode_silver.cpp\"\r\n    \"install/hashes/episode_amigo.cpp\"\r\n    \"install/hashes/mission_sonic.cpp\"\r\n    \"install/hashes/mission_shadow.cpp\"\r\n    \"install/hashes/mission_silver.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_USER_CXX_SOURCES\r\n    \"user/achievement_data.cpp\"\r\n    \"user/achievement_manager.cpp\"\r\n    \"user/config.cpp\"\r\n    \"user/registry.cpp\"\r\n    \"user/paths.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_MOD_CXX_SOURCES\r\n    \"mod/mod_loader.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_UTILS_CXX_SOURCES\r\n    \"utils/bit_stream.cpp\"\r\n    \"utils/ring_buffer.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_THIRDPARTY_SOURCES\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui/backends/imgui_impl_sdl2.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui/imgui.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui/imgui_demo.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui/imgui_draw.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui/imgui_tables.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui/imgui_widgets.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/implot/implot.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/implot/implot_demo.cpp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/implot/implot_items.cpp\"\r\n    \"${MARATHON_RECOMP_TOOLS_ROOT}/XenosRecomp/thirdparty/smol-v/source/smolv.cpp\"\r\n)\r\n\r\nset(MARATHON_RECOMP_THIRDPARTY_INCLUDES\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/concurrentqueue\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/ddspp\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/imgui\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/implot\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/json/include\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/magic_enum/include\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/stb\"\r\n    \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/unordered_dense/include\"\r\n    \"${MARATHON_RECOMP_TOOLS_ROOT}/bc_diff\"\r\n    \"${MARATHON_RECOMP_TOOLS_ROOT}/XenosRecomp/thirdparty/smol-v/source\"\r\n)\r\n\r\nset_source_files_properties(${MARATHON_RECOMP_THIRDPARTY_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS ON)\r\n\r\nset(MARATHON_RECOMP_CXX_SOURCES\r\n    \"app.cpp\"\r\n    \"exports.cpp\"\r\n    \"main.cpp\"\r\n    \"misc_impl.cpp\"\r\n    \"preload_executable.cpp\"\r\n    \"sdl_listener.cpp\"\r\n    \"stdafx.cpp\"\r\n    \"version.cpp\"\r\n    \r\n    ${MARATHON_RECOMP_KERNEL_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_LOCALE_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_OS_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_CPU_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_GPU_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_APU_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_HID_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_PATCHES_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_UI_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_INSTALL_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_USER_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_UTILS_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_MOD_CXX_SOURCES}\r\n    ${MARATHON_RECOMP_THIRDPARTY_SOURCES}\r\n)\r\n\r\ninclude(\"version.cmake\")\r\n\r\nset(VERSION_TXT \"${PROJECT_SOURCE_DIR}/res/version.txt\")\r\n\r\n# Only show Git info and build type if not Release.\r\nset(SHOW_GIT_INFO_AND_BUILD_TYPE 0)\r\nif (NOT ${CMAKE_BUILD_TYPE} MATCHES \"Release\")\r\n    set(SHOW_GIT_INFO_AND_BUILD_TYPE 1)\r\nendif()\r\n\r\nif (MARATHON_RECOMP_METAL)\r\n    set(XCRUN_TOOL \"/usr/bin/xcrun\")\r\nendif()\r\n\r\nGenerateVersionSources(\r\n    OUTPUT_DIR ${PROJECT_SOURCE_DIR}\r\n    VERSION_TXT ${VERSION_TXT}\r\n    H_TEMPLATE \"${PROJECT_SOURCE_DIR}/res/version.h.template\"\r\n    CXX_TEMPLATE \"${PROJECT_SOURCE_DIR}/res/version.cpp.template\"\r\n    BUILD_TYPE ${CMAKE_BUILD_TYPE}\r\n    SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE}\r\n    SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE}\r\n)\r\n\r\nif (WIN32)\r\n    # Create binary version number for Win32 integer attributes.\r\n    CreateVersionString(\r\n        VERSION_TXT ${VERSION_TXT}\r\n        OUTPUT_CSV 1\r\n        OUTPUT_VAR WIN32_VERSION_BINARY\r\n    )\r\n\r\n    # Create string version number for Win32 detailed attributes.\r\n    CreateVersionString(\r\n        VERSION_TXT ${VERSION_TXT}\r\n        BUILD_TYPE ${CMAKE_BUILD_TYPE}\r\n        SHOW_GIT_INFO ${SHOW_GIT_INFO_AND_BUILD_TYPE}\r\n        SHOW_BUILD_TYPE ${SHOW_GIT_INFO_AND_BUILD_TYPE}\r\n        OUTPUT_VAR WIN32_VERSION_STRING\r\n    )\r\n\r\n    # Set Win32 icon path.\r\n    set(WIN32_ICON_PATH \"${PROJECT_SOURCE_DIR}/../MarathonRecompResources/images/game_icon.ico\")\r\n\r\n    configure_file(\"res/win32/res.rc.template\" \"${CMAKE_BINARY_DIR}/res.rc\" @ONLY)\r\n    add_executable(MarathonRecomp ${MARATHON_RECOMP_CXX_SOURCES} \"${CMAKE_BINARY_DIR}/res.rc\")\r\n\r\n    # Hide console for release configurations.\r\n    if (${CMAKE_BUILD_TYPE} MATCHES \"Release\")\r\n        target_link_options(MarathonRecomp PRIVATE \"/SUBSYSTEM:WINDOWS\" \"/ENTRY:mainCRTStartup\")\r\n    endif()\r\nelseif (APPLE)\r\n    # Create version number for app bundle.\r\n    CreateVersionString(\r\n            VERSION_TXT ${VERSION_TXT}\r\n            OUTPUT_VAR MACOS_BUNDLE_VERSION\r\n    )\r\n\r\n    add_executable(MarathonRecomp MACOSX_BUNDLE\r\n            ${MARATHON_RECOMP_CXX_SOURCES}\r\n            res/macos/game_icon.icns\r\n    )\r\n    set_source_files_properties(res/macos/game_icon.icns PROPERTIES\r\n            MACOSX_PACKAGE_LOCATION Resources)\r\n    set_target_properties(MarathonRecomp PROPERTIES\r\n            OUTPUT_NAME \"Marathon Recompiled\"\r\n            MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/res/macos/MacOSXBundleInfo.plist.in\r\n            MACOSX_BUNDLE_GUI_IDENTIFIER sonicnext-dev.MarathonRecomp\r\n            MACOSX_BUNDLE_BUNDLE_NAME \"Marathon Recompiled\"\r\n            MACOSX_BUNDLE_BUNDLE_VERSION ${MACOS_BUNDLE_VERSION}\r\n            MACOSX_BUNDLE_SHORT_VERSION_STRING ${MACOS_BUNDLE_VERSION}\r\n            MACOSX_BUNDLE_ICON_FILE \"game_icon.icns\"\r\n    )\r\n\r\n    # Linking with MoltenVK directly would prevent using the system Vulkan loader to load with debug layers.\r\n    # Instead, copy the MoltenVK dylib to the app bundle along with an ICD file for the loader to find it.\r\n    # In the event the loader is not installed, the MoltenVK dylib can still be picked up directly in the app bundle.\r\n    set(MVK_BUNDLED_PATH \"Resources/vulkan/icd.d\")\r\n    set(MVK_DST \"${CMAKE_CURRENT_BINARY_DIR}/Marathon Recompiled.app/Contents/${MVK_BUNDLED_PATH}\")\r\n    set_property(TARGET MarathonRecomp APPEND PROPERTY BUILD_RPATH \"@executable_path/../${MVK_BUNDLED_PATH}\")\r\n\r\n    set(MVK_ICD_SRC \"${MARATHON_RECOMP_THIRDPARTY_ROOT}/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json\")\r\n    set(MVK_ICD_DST \"${MVK_DST}/MoltenVK_icd.json\")\r\n    set(MVK_DYLIB_SRC \"${CMAKE_BINARY_DIR}/thirdparty/MoltenVK/libMoltenVK.dylib\")\r\n    set(MVK_DYLIB_DST \"${MVK_DST}/libMoltenVK.dylib\")\r\n\r\n    add_custom_command(\r\n            OUTPUT ${MVK_DST}\r\n            COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})\r\n    add_custom_command(\r\n            OUTPUT ${MVK_ICD_DST}\r\n            DEPENDS ${MVK_ICD_SRC} ${MVK_DST}\r\n            COMMAND ${CMAKE_COMMAND} -E copy ${MVK_ICD_SRC} ${MVK_ICD_DST})\r\n    add_custom_command(\r\n            OUTPUT ${MVK_DYLIB_DST}\r\n            DEPENDS ${MVK_DYLIB_SRC} ${MVK_DST}\r\n            COMMAND ${CMAKE_COMMAND} -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST})\r\n    add_custom_target(CopyMoltenVK DEPENDS ${MVK_ICD_DST} ${MVK_DYLIB_DST})\r\n    add_dependencies(CopyMoltenVK MoltenVK)\r\n    add_dependencies(MarathonRecomp CopyMoltenVK)\r\nelse()\r\n    add_executable(MarathonRecomp ${MARATHON_RECOMP_CXX_SOURCES})\r\nendif()\r\n\r\nif (MARATHON_RECOMP_FLATPAK)\r\n    target_compile_definitions(MarathonRecomp PRIVATE \r\n        \"MARATHON_RECOMP_FLATPAK\"\r\n        \"GAME_INSTALL_DIRECTORY=\\\"/var/data\\\"\"\r\n    )\r\nendif()\r\n\r\nfind_package(CURL REQUIRED)\r\n\r\nif (MARATHON_RECOMP_METAL)\r\n    target_compile_definitions(MarathonRecomp PRIVATE MARATHON_RECOMP_METAL)\r\nendif()\r\n\r\nif (MARATHON_RECOMP_D3D12)\r\n    find_package(directx-headers CONFIG REQUIRED)\r\n    find_package(directx12-agility CONFIG REQUIRED)\r\n    target_compile_definitions(MarathonRecomp PRIVATE MARATHON_RECOMP_D3D12)\r\n\r\n    file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/D3D12)\r\n    add_custom_command(TARGET MarathonRecomp POST_BUILD\r\n            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DirectX12-Core,IMPORTED_LOCATION_RELEASE> $<TARGET_FILE_DIR:MarathonRecomp>/D3D12\r\n            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DirectX12-Layers,IMPORTED_LOCATION_DEBUG> $<TARGET_FILE_DIR:MarathonRecomp>/D3D12\r\n            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DirectXShaderCompiler,IMPORTED_LOCATION> $<TARGET_FILE_DIR:MarathonRecomp>\r\n            COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_PROPERTY:Microsoft::DXIL,IMPORTED_LOCATION> $<TARGET_FILE_DIR:MarathonRecomp>\r\n            COMMAND_EXPAND_LISTS\r\n    )\r\n\r\n    target_link_libraries(MarathonRecomp PRIVATE\r\n            Microsoft::DirectXShaderCompiler\r\n            Microsoft::DXIL\r\n            dxgi\r\n    )\r\nendif()\r\n\r\nif (WIN32)\r\n    target_link_libraries(MarathonRecomp PRIVATE\r\n        comctl32\r\n        dwmapi\r\n        ntdll\r\n        Shcore\r\n        Synchronization\r\n        winmm\r\n        windowsapp\r\n    )\r\nendif()\r\n\r\ntarget_link_libraries(MarathonRecomp PRIVATE\r\n    fmt::fmt\r\n    libzstd_static\r\n    msdf-atlas-gen::msdf-atlas-gen\r\n    nfd::nfd\r\n    o1heap\r\n    XenonUtils\r\n    SDL2::SDL2-static\r\n    SDL2_mixer\r\n    tomlplusplus::tomlplusplus\r\n    MarathonRecompLib\r\n    xxHash::xxhash\r\n    CURL::libcurl\r\n    plume\r\n)\r\n\r\ntarget_link_libraries(MarathonRecomp PRIVATE ffmpeg)\r\n\r\ntarget_include_directories(MarathonRecomp PRIVATE\r\n    ${CMAKE_CURRENT_SOURCE_DIR}\r\n    \"${CMAKE_CURRENT_SOURCE_DIR}/api\"\r\n    ${MARATHON_RECOMP_THIRDPARTY_INCLUDES}\r\n)\r\n\r\nif (CMAKE_SYSTEM_NAME MATCHES \"Linux\")\r\n    find_package(PkgConfig REQUIRED)\r\n    find_package(X11 REQUIRED)\r\n    pkg_search_module(GLIB REQUIRED glib-2.0)\r\n    pkg_search_module(GIO REQUIRED gio-2.0)\r\n    target_include_directories(MarathonRecomp PRIVATE ${X11_INCLUDE_DIR} ${GLIB_INCLUDE_DIRS} ${GIO_INCLUDE_DIRS})\r\n    target_link_libraries(MarathonRecomp PRIVATE ${X11_LIBRARIES} ${GLIB_LIBRARIES} ${GIO_LIBRARIES})\r\nendif()\r\n\r\ntarget_precompile_headers(MarathonRecomp PUBLIC ${MARATHON_RECOMP_PRECOMPILED_HEADERS})\r\n\r\nfunction(compile_shader FILE_PATH TARGET_NAME)\r\n    set(HLSL_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/hlsl/${FILE_PATH}.hlsl)\r\n    set(MSL_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/gpu/shader/msl/${FILE_PATH}.metal)\r\n    cmake_path(GET HLSL_FILE_PATH STEM HLSL_NAME)\r\n    cmake_path(GET MSL_FILE_PATH STEM MSL_NAME)\r\n    if (MARATHON_RECOMP_METAL)\r\n        add_custom_command(\r\n                OUTPUT ${MSL_FILE_PATH}.ir\r\n                COMMAND ${XCRUN_TOOL} -sdk macosx metal -o ${MSL_FILE_PATH}.ir -c ${MSL_FILE_PATH} -D__air__ -frecord-sources -gline-tables-only\r\n                DEPENDS ${MSL_FILE_PATH}\r\n        )\r\n        add_custom_command(\r\n                OUTPUT ${MSL_FILE_PATH}.metallib\r\n                COMMAND ${XCRUN_TOOL} -sdk macosx metallib -o ${MSL_FILE_PATH}.metallib ${MSL_FILE_PATH}.ir\r\n                DEPENDS ${MSL_FILE_PATH}.ir\r\n        )\r\n        BIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${MSL_FILE_PATH}.metallib\" DEST_FILE \"${MSL_FILE_PATH}.metallib\" ARRAY_NAME \"g_${MSL_NAME}_air\")\r\n    endif()\r\n    if (MARATHON_RECOMP_D3D12)\r\n        add_custom_command(\r\n            OUTPUT ${HLSL_FILE_PATH}.dxil.h\r\n            COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -Wno-ignored-attributes -E shaderMain -Fh ${HLSL_FILE_PATH}.dxil.h ${HLSL_FILE_PATH} -Vn g_${HLSL_NAME}_dxil\r\n            DEPENDS ${HLSL_FILE_PATH}\r\n        )\r\n        target_sources(MarathonRecomp PRIVATE ${HLSL_FILE_PATH}.dxil.h)\r\n    endif()\r\n    add_custom_command(\r\n        OUTPUT ${HLSL_FILE_PATH}.spirv.h\r\n        COMMAND ${DIRECTX_DXC_TOOL} -T ${TARGET_NAME} -HV 2021 -all-resources-bound -spirv -fvk-use-dx-layout ${ARGN} -E shaderMain -Fh ${HLSL_FILE_PATH}.spirv.h ${HLSL_FILE_PATH} -Vn g_${HLSL_NAME}_spirv\r\n        DEPENDS ${HLSL_FILE_PATH}\r\n    )\r\n    target_sources(MarathonRecomp PRIVATE ${HLSL_FILE_PATH}.spirv.h)\r\nendfunction()\r\n\r\nfunction(compile_vertex_shader FILE_PATH)\r\n    compile_shader(${FILE_PATH} vs_6_0 -fvk-invert-y -DMARATHON_RECOMP)\r\nendfunction()\r\n\r\nfunction(compile_pixel_shader FILE_PATH)\r\n    compile_shader(${FILE_PATH} ps_6_0 -DMARATHON_RECOMP)\r\nendfunction()\r\n\r\ncompile_pixel_shader(blend_color_alpha_ps)\r\ncompile_pixel_shader(conditional_survey_ps)\r\ncompile_vertex_shader(copy_vs)\r\ncompile_pixel_shader(copy_color_ps)\r\ncompile_pixel_shader(copy_depth_ps)\r\ncompile_pixel_shader(csd_filter_ps)\r\ncompile_vertex_shader(csd_no_tex_vs)\r\ncompile_vertex_shader(csd_vs)\r\ncompile_vertex_shader(enhanced_burnout_blur_vs)\r\ncompile_pixel_shader(enhanced_burnout_blur_ps)\r\ncompile_pixel_shader(gaussian_blur_3x3)\r\ncompile_pixel_shader(gaussian_blur_5x5)\r\ncompile_pixel_shader(gaussian_blur_7x7)\r\ncompile_pixel_shader(gaussian_blur_9x9)\r\ncompile_pixel_shader(gamma_correction_ps)\r\ncompile_pixel_shader(imgui_ps)\r\ncompile_vertex_shader(imgui_vs)\r\ncompile_pixel_shader(resolve_msaa_color_2x)\r\ncompile_pixel_shader(resolve_msaa_color_4x)\r\ncompile_pixel_shader(resolve_msaa_color_8x)\r\ncompile_pixel_shader(resolve_msaa_depth_2x)\r\ncompile_pixel_shader(resolve_msaa_depth_4x)\r\ncompile_pixel_shader(resolve_msaa_depth_8x)\r\n\r\nfunction(generate_aggregate_header INPUT_DIRECTORY OUTPUT_FILE)\r\n    get_filename_component(ABS_OUTPUT_FILE \"${OUTPUT_FILE}\" ABSOLUTE)\r\n    file(GLOB_RECURSE HEADER_FILES \"${INPUT_DIRECTORY}/*.h\")\r\n    set(HEADER_CONTENT \"#pragma once\\n\\n\")\r\n\r\n    foreach(HEADER_FILE IN LISTS HEADER_FILES)\r\n        get_filename_component(ABS_HEADER_FILE \"${HEADER_FILE}\" ABSOLUTE)\r\n        if (ABS_HEADER_FILE STREQUAL ABS_OUTPUT_FILE)\r\n            continue()\r\n        endif()\r\n        file(RELATIVE_PATH RELATIVE_HEADER_FILE \"${INPUT_DIRECTORY}\" \"${HEADER_FILE}\")\r\n        string(APPEND HEADER_CONTENT \"#include \\\"${RELATIVE_HEADER_FILE}\\\"\\n\")\r\n    endforeach()\r\n\r\n    if (EXISTS \"${OUTPUT_FILE}\")\r\n        file(READ \"${OUTPUT_FILE}\" EXISTING_CONTENT)\r\n        if (EXISTING_CONTENT STREQUAL HEADER_CONTENT)\r\n            return()\r\n        endif()\r\n    endif()\r\n\r\n    file(WRITE \"${OUTPUT_FILE}\" \"${HEADER_CONTENT}\")\r\nendfunction()\r\n\r\ngenerate_aggregate_header(\r\n    \"${CMAKE_CURRENT_SOURCE_DIR}/api\"\r\n    \"${CMAKE_CURRENT_SOURCE_DIR}/api/Marathon.h\"\r\n)\r\n\r\nset(RESOURCES_SOURCE_PATH \"${PROJECT_SOURCE_DIR}/../MarathonRecompResources\")\r\nset(RESOURCES_OUTPUT_PATH \"${PROJECT_SOURCE_DIR}/res\")\r\n\r\n## Miscellaneous ##\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/bc_diff/button_bc_diff.bin\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/bc_diff/button_bc_diff.bin\" ARRAY_NAME \"g_button_bc_diff\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/font/im_font_atlas.bin\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.bin\" ARRAY_NAME \"g_im_font_atlas\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/font/im_font_atlas.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/font/im_font_atlas.dds\" ARRAY_NAME \"g_im_font_atlas_texture\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/button_window.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/button_window.dds\" ARRAY_NAME \"g_button_window\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/controller.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/controller.dds\" ARRAY_NAME \"g_controller\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/sonicnext-dev.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/sonicnext-dev.dds\" ARRAY_NAME \"g_sonicnextdev\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/kbm.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/kbm.dds\" ARRAY_NAME \"g_kbm\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/main_menu1.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/main_menu1.dds\" ARRAY_NAME \"g_main_menu1\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/main_menu7.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/main_menu7.dds\" ARRAY_NAME \"g_main_menu7\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/main_menu8.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/main_menu8.dds\" ARRAY_NAME \"g_main_menu8\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/main_menu9.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/main_menu9.dds\" ARRAY_NAME \"g_main_menu9\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/arrow.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/arrow.dds\" ARRAY_NAME \"g_arrow\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/window.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/window.dds\" ARRAY_NAME \"g_window\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/common/select_arrow.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/common/select_arrow.dds\" ARRAY_NAME \"g_select_arrow\" COMPRESSION_TYPE \"zstd\")\r\n\r\n## Installer ##\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_001.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_001.dds\" ARRAY_NAME \"g_install_001\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_002.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_002.dds\" ARRAY_NAME \"g_install_002\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_003.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_003.dds\" ARRAY_NAME \"g_install_003\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_004.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_004.dds\" ARRAY_NAME \"g_install_004\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_005.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_005.dds\" ARRAY_NAME \"g_install_005\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_006.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_006.dds\" ARRAY_NAME \"g_install_006\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_007.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_007.dds\" ARRAY_NAME \"g_install_007\" COMPRESSION_TYPE \"zstd\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/installer/install_008.dds\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/installer/install_008.dds\" ARRAY_NAME \"g_install_008\" COMPRESSION_TYPE \"zstd\")\r\n\r\n## Game Icon ##\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/images/game_icon.bmp\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/images/game_icon.bmp\" ARRAY_NAME \"g_game_icon\")\r\n\r\n## Audio ##\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/music/installer.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/music/installer.ogg\" ARRAY_NAME \"g_installer_music\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/window_open.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/window_open.ogg\" ARRAY_NAME \"g_window_open\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/window_close.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/window_close.ogg\" ARRAY_NAME \"g_window_close\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/cursor.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/cursor.ogg\" ARRAY_NAME \"g_cursor\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/deside.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/deside.ogg\" ARRAY_NAME \"g_deside\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/move.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/move.ogg\" ARRAY_NAME \"g_move\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/main_deside.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/main_deside.ogg\" ARRAY_NAME \"g_main_deside\")\r\nBIN2C(TARGET_OBJ MarathonRecomp SOURCE_FILE \"${RESOURCES_SOURCE_PATH}/sounds/cannot_deside.ogg\" DEST_FILE \"${RESOURCES_OUTPUT_PATH}/sounds/cannot_deside.ogg\" ARRAY_NAME \"g_cannot_deside\")\r\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Core/csdBase.h",
    "content": "#pragma once\n\nnamespace Chao::CSD\n{\n    class CBase {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Core/csdRCObject.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Core/csdRCPtrAbs.h>\n\nnamespace Chao::CSD\n{\n    class RCPtrAbs::RCObject\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpFree;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        xpointer<void> m_pMemory;\n        be<uint32_t> m_ReferenceCount;\n        xpointer<void> m_Field0C;\n        be<uint32_t> m_Field10;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs::RCObject, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs::RCObject, m_pMemory, 0x04);\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs::RCObject, m_ReferenceCount, 0x08);\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs::RCObject, m_Field0C, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs::RCObject, m_Field10, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Core/csdRCObjectImp.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Core/csdRCPtr.h>\n\nnamespace Chao::CSD\n{\n    template<typename T>\n    class RCPtr<T>::RCObjectImp : public RCObject {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Core/csdRCPtr.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Core/csdRCPtrAbs.h>\n\nnamespace Chao::CSD\n{\n    template<typename T>\n    class RCPtr : RCPtrAbs\n    {\n    public:\n        class RCObjectImp;\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Core/csdRCPtrAbs.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Chao::CSD\n{\n    class RCPtrAbs\n    {\n    public:\n        class RCObject;\n\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpCreateRCObject;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        xpointer<RCObject> m_pObject;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(RCPtrAbs, m_pObject, 0x04);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Core/csdTexList.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Core/csdBase.h>\n#include <Chao/CSD/Core/csdRCPtr.h>\n\nnamespace Chao::CSD\n{\n    class CTexList : public CBase\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        RCPtr<uint8_t> m_pRCData;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CTexList, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CTexList, m_pRCData, 0x04);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmMotionPattern.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Chao::CSD\n{\n    class CMotionPattern : CBase {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmNode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Chao/CSD/Core/csdBase.h>\n#include <Chao/CSD/Manager/csdmResourceBase.h>\n#include <Chao/CSD/Manager/csdmNodeObserver.h>\n#include <Chao/CSD/Manager/csdmSubjectBase.h>\n\nnamespace Chao::CSD\n{\n    struct Node;\n\n    class CNode : public CResourceBase<Node>, SubjectBase<CNodeObserver, CNode>, CBase\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x34);\n        xpointer<CMotionPattern> m_pMotionPattern;\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CNode, m_pMotionPattern, 0x50);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmNodeObserver.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Manager/csdmObserverBase.h>\n\nnamespace Chao::CSD\n{\n    class CNode;\n\n    class CNodeObserver : public CObserverBase<CNode> {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmObserverBase.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Chao::CSD\n{\n    template<typename TObservee>\n    class CObserverBase\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CObserverBase<void>, m_pVftable, 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmProject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Chao/CSD/Core/csdBase.h>\n#include <Chao/CSD/Manager/csdmResourceBase.h>\n\nnamespace Chao::CSD\n{\n    class SceneNode;\n    class CProject;\n    class CScene;\n    class CTexList;\n\n    struct Project\n    {\n        xpointer<SceneNode> pRootNode;\n    };\n\n    class CProject : public CResourceBase<Project>, CBase\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1C);\n        RCPtr<CTexList> m_rcTexList;\n        MARATHON_INSERT_PADDING(0x1C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Project, pRootNode, 0x00);\n\n    MARATHON_ASSERT_OFFSETOF(CProject, m_rcTexList, 0x28);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmResourceBase.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Core/csdRCPtr.h>\n\nnamespace Chao::CSD\n{\n    template<typename T>\n    class CResourceBase\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpCopyResource;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<T> m_pResource;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CResourceBase<void>, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CResourceBase<void>, m_pResource, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmScene.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Chao/CSD/Core/csdBase.h>\n#include <Chao/CSD/Manager/csdmResourceBase.h>\n#include <Chao/CSD/Manager/csdmSceneObserver.h>\n#include <Chao/CSD/Manager/csdmSubjectBase.h>\n\nnamespace Chao::CSD\n{\n    class CScene;\n    class CNode;\n\n    struct Cast\n    {\n        MARATHON_INSERT_PADDING(0x144);\n    };\n\n    struct CastLink\n    {\n        be<uint32_t> ChildCastIndex;\n        be<uint32_t> SiblingCastIndex;\n    };\n\n    struct CastNode\n    {\n        be<uint32_t> CastCount;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<xpointer<Cast>> pCasts;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> RootCastIndex;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<CastLink> pCastLinks;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    struct CastIndex\n    {\n        xpointer<const char> pCastName;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> CastNodeIndex;\n        be<uint32_t> CastIndex;\n    };\n\n    struct Scene\n    {\n        MARATHON_INSERT_PADDING(8);\n        be<float> FPS;\n        MARATHON_INSERT_PADDING(0x24);\n        be<uint32_t> CastNodeCount;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<CastNode> pCastNodes;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> CastCount;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<CastIndex> pCastIndices;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> AnimationCount;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<void> pAnimationKeyFrameDataList;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<void> pAnimationDictionary;\n        MARATHON_INSERT_PADDING(4);\n        be<float> AspectRatio;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<void> pAnimationFrameDataList;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    struct SceneIndex\n    {\n        xpointer<const char> pSceneName;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> SceneIndex;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    struct SceneNodeIndex\n    {\n        xpointer<const char> pSceneNodeName;\n        be<uint32_t> SceneNodeIndex;\n    };\n\n    struct SceneNode\n    {\n        be<uint32_t> SceneCount;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<xpointer<Scene>> pScenes;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<SceneIndex> pSceneIndices;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> SceneNodeCount;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<SceneNode> pSceneNodes;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<SceneNodeIndex> pSceneNodeIndices;\n    };\n\n    enum MotionRepeatType : uint32_t\n    {\n        MotionRepeatType_PlayOnce,\n        MotionRepeatType_Loop,\n        MotionRepeatType_PingPong,\n        MotionRepeatType_PlayThenDestroy\n    };\n\n    class CScene : public CResourceBase<Scene>, SubjectBase<CSceneObserver, CScene>, CBase\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x60);\n        be<float> m_PrevMotionFrame;\n        be<float> m_MotionFrame;\n        be<float> m_MotionSpeed;\n        be<float> m_MotionStartFrame;\n        be<float> m_MotionEndFrame;\n        MARATHON_INSERT_PADDING(0x0C);\n        be<uint32_t> m_MotionDisableFlag;\n        MARATHON_INSERT_PADDING(0x10);\n        be<MotionRepeatType> m_MotionRepeatType;\n        MARATHON_INSERT_PADDING(0x2C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CastLink, ChildCastIndex, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CastLink, SiblingCastIndex, 0x04);\n\n    MARATHON_ASSERT_OFFSETOF(CastNode, CastCount, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CastNode, pCasts, 0x08);\n    MARATHON_ASSERT_OFFSETOF(CastNode, RootCastIndex, 0x10);\n    MARATHON_ASSERT_OFFSETOF(CastNode, pCastLinks, 0x18);\n\n    MARATHON_ASSERT_OFFSETOF(CastIndex, pCastName, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CastIndex, CastNodeIndex, 0x08);\n    MARATHON_ASSERT_OFFSETOF(CastIndex, CastIndex, 0x0C);\n\n    MARATHON_ASSERT_OFFSETOF(Scene, FPS, 0x08);\n    MARATHON_ASSERT_OFFSETOF(Scene, CastNodeCount, 0x30);\n    MARATHON_ASSERT_OFFSETOF(Scene, pCastNodes, 0x38);\n    MARATHON_ASSERT_OFFSETOF(Scene, CastCount, 0x40);\n    MARATHON_ASSERT_OFFSETOF(Scene, pCastIndices, 0x48);\n    MARATHON_ASSERT_OFFSETOF(Scene, AnimationCount, 0x50);\n    MARATHON_ASSERT_OFFSETOF(Scene, pAnimationKeyFrameDataList, 0x58);\n    MARATHON_ASSERT_OFFSETOF(Scene, pAnimationDictionary, 0x60);\n    MARATHON_ASSERT_OFFSETOF(Scene, AspectRatio, 0x68);\n    MARATHON_ASSERT_OFFSETOF(Scene, pAnimationFrameDataList, 0x70);\n\n    MARATHON_ASSERT_OFFSETOF(SceneIndex, pSceneName, 0x00);\n    MARATHON_ASSERT_OFFSETOF(SceneIndex, SceneIndex, 0x08);\n\n    MARATHON_ASSERT_OFFSETOF(SceneNodeIndex, pSceneNodeName, 0x00);\n    MARATHON_ASSERT_OFFSETOF(SceneNodeIndex, SceneNodeIndex, 0x04);\n\n    MARATHON_ASSERT_OFFSETOF(SceneNode, SceneCount, 0x00);\n    MARATHON_ASSERT_OFFSETOF(SceneNode, pScenes, 0x08);\n    MARATHON_ASSERT_OFFSETOF(SceneNode, pSceneIndices, 0x10);\n    MARATHON_ASSERT_OFFSETOF(SceneNode, SceneNodeCount, 0x18);\n    MARATHON_ASSERT_OFFSETOF(SceneNode, pSceneNodes, 0x20);\n    MARATHON_ASSERT_OFFSETOF(SceneNode, pSceneNodeIndices, 0x28);\n\n    MARATHON_ASSERT_OFFSETOF(CScene, m_PrevMotionFrame, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(CScene, m_MotionFrame, 0x80);\n    MARATHON_ASSERT_OFFSETOF(CScene, m_MotionSpeed, 0x84);\n    MARATHON_ASSERT_OFFSETOF(CScene, m_MotionStartFrame, 0x88);\n    MARATHON_ASSERT_OFFSETOF(CScene, m_MotionEndFrame, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(CScene, m_MotionDisableFlag, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(CScene, m_MotionRepeatType, 0xB0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmSceneObserver.h",
    "content": "#pragma once\n\n#include <Chao/CSD/Manager/csdmObserverBase.h>\n\nnamespace Chao::CSD\n{\n    class CScene;\n\n    class CSceneObserver : public CObserverBase<CScene> {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Chao/CSD/Manager/csdmSubjectBase.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Chao::CSD\n{\n    template<typename TObserver, typename TObservee>\n    class SubjectBase\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpGetObservee;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    static_assert(__builtin_offsetof(SubjectBase<void, void>, m_pVftable) == 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Marathon.h",
    "content": "#pragma once\n\n#include \"Chao/CSD/Core/csdBase.h\"\n#include \"Chao/CSD/Core/csdRCObject.h\"\n#include \"Chao/CSD/Core/csdRCObjectImp.h\"\n#include \"Chao/CSD/Core/csdRCPtr.h\"\n#include \"Chao/CSD/Core/csdRCPtrAbs.h\"\n#include \"Chao/CSD/Core/csdTexList.h\"\n#include \"Chao/CSD/Manager/csdmMotionPattern.h\"\n#include \"Chao/CSD/Manager/csdmNode.h\"\n#include \"Chao/CSD/Manager/csdmNodeObserver.h\"\n#include \"Chao/CSD/Manager/csdmObserverBase.h\"\n#include \"Chao/CSD/Manager/csdmProject.h\"\n#include \"Chao/CSD/Manager/csdmResourceBase.h\"\n#include \"Chao/CSD/Manager/csdmScene.h\"\n#include \"Chao/CSD/Manager/csdmSceneObserver.h\"\n#include \"Chao/CSD/Manager/csdmSubjectBase.h\"\n#include \"Sonicteam/Actor.h\"\n#include \"Sonicteam/ActorManager.h\"\n#include \"Sonicteam/AlertWindowTask.h\"\n#include \"Sonicteam/AppMarathon.h\"\n#include \"Sonicteam/AudioEngineXenon.h\"\n#include \"Sonicteam/ButtonWindowTask.h\"\n#include \"Sonicteam/CObjBalloonIconDrawable.h\"\n#include \"Sonicteam/Camera/CameraMode.h\"\n#include \"Sonicteam/Camera/CameraModeManager.h\"\n#include \"Sonicteam/Camera/Cameraman.h\"\n#include \"Sonicteam/Camera/SonicCamera.h\"\n#include \"Sonicteam/CommonObjectHint.h\"\n#include \"Sonicteam/CsdLink.h\"\n#include \"Sonicteam/CsdManager.h\"\n#include \"Sonicteam/CsdObject.h\"\n#include \"Sonicteam/CsdResource.h\"\n#include \"Sonicteam/DocMarathonImp.h\"\n#include \"Sonicteam/DocMarathonState.h\"\n#include \"Sonicteam/Enemy/EnemyShot.h\"\n#include \"Sonicteam/Enemy/EnemyShotNormal.h\"\n#include \"Sonicteam/Enemy/EnemyShotPoint.h\"\n#include \"Sonicteam/Fixture.h\"\n#include \"Sonicteam/Game.h\"\n#include \"Sonicteam/GameImp.h\"\n#include \"Sonicteam/GameMode.h\"\n#include \"Sonicteam/Globals.h\"\n#include \"Sonicteam/HUDButtonWindow.h\"\n#include \"Sonicteam/HUDCALLBACK.h\"\n#include \"Sonicteam/HUDGoldMedal.h\"\n#include \"Sonicteam/HUDLimitTime.h\"\n#include \"Sonicteam/HUDLoading.h\"\n#include \"Sonicteam/HUDMainDisplay.h\"\n#include \"Sonicteam/HUDMainMenu.h\"\n#include \"Sonicteam/HUDMessageWindow.h\"\n#include \"Sonicteam/HUDOption.h\"\n#include \"Sonicteam/HUDPopupScreen.h\"\n#include \"Sonicteam/HUDRaderMap.h\"\n#include \"Sonicteam/HUDStageTitle.h\"\n#include \"Sonicteam/HintWindowTask.h\"\n#include \"Sonicteam/HudTextParts.h\"\n#include \"Sonicteam/ImageFilter.h\"\n#include \"Sonicteam/MainDisplayTask.h\"\n#include \"Sonicteam/MainMenuExpositionTask.h\"\n#include \"Sonicteam/MainMenuTask.h\"\n#include \"Sonicteam/MainMode.h\"\n#include \"Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h\"\n#include \"Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h\"\n#include \"Sonicteam/Message/HUDGoldMedal/MsgChangeState.h\"\n#include \"Sonicteam/Message/HUDMainMenu/MsgChangeState.h\"\n#include \"Sonicteam/Message/HUDMainMenu/MsgSetCursor.h\"\n#include \"Sonicteam/Message/HUDMainMenu/MsgTransition.h\"\n#include \"Sonicteam/Message/Mission/MsgGetGlobalFlag.h\"\n#include \"Sonicteam/Message/ObjJump123/MsgGetNextPoint.h\"\n#include \"Sonicteam/Message/PauseAdapter/MsgGetText.h\"\n#include \"Sonicteam/Message/Player/MsgSuckPlayer.h\"\n#include \"Sonicteam/MessageWindowTask.h\"\n#include \"Sonicteam/Mission/Core.h\"\n#include \"Sonicteam/MovieObject.h\"\n#include \"Sonicteam/MovieObjectWmv.h\"\n#include \"Sonicteam/MovieTask.h\"\n#include \"Sonicteam/MyCue.h\"\n#include \"Sonicteam/MyCueAdx.h\"\n#include \"Sonicteam/MyCueAttenuate.h\"\n#include \"Sonicteam/MyGraphicsDevice.h\"\n#include \"Sonicteam/MyPhantom.h\"\n#include \"Sonicteam/MyRenderProcess.h\"\n#include \"Sonicteam/MyTexture.h\"\n#include \"Sonicteam/MyTransforms.h\"\n#include \"Sonicteam/NamedActor.h\"\n#include \"Sonicteam/NamedTask.h\"\n#include \"Sonicteam/NoSyncThread.h\"\n#include \"Sonicteam/ObjectVehicle.h\"\n#include \"Sonicteam/ObjectVehicleBike.h\"\n#include \"Sonicteam/PauseAdapter.h\"\n#include \"Sonicteam/PauseTask.h\"\n#include \"Sonicteam/Player/GroundRayListener.h\"\n#include \"Sonicteam/Player/ICollisionListener.h\"\n#include \"Sonicteam/Player/ICollisionListenerTemplate.h\"\n#include \"Sonicteam/Player/IDynamicLink.h\"\n#include \"Sonicteam/Player/IEventerListener.h\"\n#include \"Sonicteam/Player/IExportExternalFlag.h\"\n#include \"Sonicteam/Player/IExportPostureRequestFlag.h\"\n#include \"Sonicteam/Player/IExportWeaponRequestFlag.h\"\n#include \"Sonicteam/Player/IFlagCommunicator.h\"\n#include \"Sonicteam/Player/IGauge.h\"\n#include \"Sonicteam/Player/INotification.h\"\n#include \"Sonicteam/Player/IPlugIn.h\"\n#include \"Sonicteam/Player/IPostureControl.h\"\n#include \"Sonicteam/Player/IPosturePlugIn.h\"\n#include \"Sonicteam/Player/IPostureSupportEdge.h\"\n#include \"Sonicteam/Player/IPostureSupportInput.h\"\n#include \"Sonicteam/Player/IPostureSupportOttoto.h\"\n#include \"Sonicteam/Player/IPostureSupportRayTemplate.h\"\n#include \"Sonicteam/Player/IPostureSupportSphere.h\"\n#include \"Sonicteam/Player/IScore.h\"\n#include \"Sonicteam/Player/IStepable.h\"\n#include \"Sonicteam/Player/IVariable.h\"\n#include \"Sonicteam/Player/IZock.h\"\n#include \"Sonicteam/Player/Input/IListener.h\"\n#include \"Sonicteam/Player/Input/ListenerNormal.h\"\n#include \"Sonicteam/Player/Input/TimedAction.h\"\n#include \"Sonicteam/Player/Object.h\"\n#include \"Sonicteam/Player/PostureControl.h\"\n#include \"Sonicteam/Player/RootFrame.h\"\n#include \"Sonicteam/Player/Score.h\"\n#include \"Sonicteam/Player/SonicGauge.h\"\n#include \"Sonicteam/Player/State/CommonContext.h\"\n#include \"Sonicteam/Player/State/CommonFall.h\"\n#include \"Sonicteam/Player/State/CommonObject.h\"\n#include \"Sonicteam/Player/State/ContextSpeedAndJump.h\"\n#include \"Sonicteam/Player/State/ICommonContext.h\"\n#include \"Sonicteam/Player/State/ICommonContextIF.h\"\n#include \"Sonicteam/Player/State/IContext.h\"\n#include \"Sonicteam/Player/State/IMachine.h\"\n#include \"Sonicteam/Player/State/Machine2.h\"\n#include \"Sonicteam/Player/State/Object2.h\"\n#include \"Sonicteam/Player/State/SonicContext.h\"\n#include \"Sonicteam/Player/State/SonicObject.h\"\n#include \"Sonicteam/Player/State/TailsContext.h\"\n#include \"Sonicteam/Player/State/TailsFlight.h\"\n#include \"Sonicteam/Player/Unit/ITestCase.h\"\n#include \"Sonicteam/Player/Weapon/SonicWeapons.h\"\n#include \"Sonicteam/Player/Zock.h\"\n#include \"Sonicteam/PropFixture.h\"\n#include \"Sonicteam/RaderMapManager.h\"\n#include \"Sonicteam/RenderAction/ApplyBloom.h\"\n#include \"Sonicteam/RenderAction/ApplyDevice.h\"\n#include \"Sonicteam/RenderAction/ApplySceneParams.h\"\n#include \"Sonicteam/RenderAction/AutoSetAspect.h\"\n#include \"Sonicteam/RenderAction/Capture.h\"\n#include \"Sonicteam/RenderAction/ClearRenderTarget.h\"\n#include \"Sonicteam/RenderAction/ColorFill.h\"\n#include \"Sonicteam/RenderAction/CopyTexture.h\"\n#include \"Sonicteam/RenderAction/LockBlendMode.h\"\n#include \"Sonicteam/RenderAction/LockCullMode.h\"\n#include \"Sonicteam/RenderAction/LockZMode.h\"\n#include \"Sonicteam/RenderAction/MakeBloom.h\"\n#include \"Sonicteam/RenderAction/Movie.h\"\n#include \"Sonicteam/RenderAction/PrepareCalculateCSM.h\"\n#include \"Sonicteam/RenderAction/Rasterize.h\"\n#include \"Sonicteam/RenderAction/RasterizeBurnoutBlur.h\"\n#include \"Sonicteam/RenderAction/ResetRenderStates.h\"\n#include \"Sonicteam/RenderAction/ResetScissorRect.h\"\n#include \"Sonicteam/RenderAction/ResetViewport.h\"\n#include \"Sonicteam/RenderAction/Resolve.h\"\n#include \"Sonicteam/RenderAction/RunCommandBuffer.h\"\n#include \"Sonicteam/RenderAction/SetAutoZPass.h\"\n#include \"Sonicteam/RenderAction/SetBackStencilOp.h\"\n#include \"Sonicteam/RenderAction/SetBlendMode.h\"\n#include \"Sonicteam/RenderAction/SetCSMTextures.h\"\n#include \"Sonicteam/RenderAction/SetClip.h\"\n#include \"Sonicteam/RenderAction/SetColorWriteEnable.h\"\n#include \"Sonicteam/RenderAction/SetConstantShader.h\"\n#include \"Sonicteam/RenderAction/SetCullMode.h\"\n#include \"Sonicteam/RenderAction/SetCurrentScreen.h\"\n#include \"Sonicteam/RenderAction/SetDepthTextures.h\"\n#include \"Sonicteam/RenderAction/SetFovY.h\"\n#include \"Sonicteam/RenderAction/SetFrameBufferObject.h\"\n#include \"Sonicteam/RenderAction/SetReflectionTextures.h\"\n#include \"Sonicteam/RenderAction/SetScissorRect.h\"\n#include \"Sonicteam/RenderAction/SetScissorTest.h\"\n#include \"Sonicteam/RenderAction/SetScreen.h\"\n#include \"Sonicteam/RenderAction/SetShaderGPRAllocation.h\"\n#include \"Sonicteam/RenderAction/SetStencilEnable.h\"\n#include \"Sonicteam/RenderAction/SetStencilFunc.h\"\n#include \"Sonicteam/RenderAction/SetStencilOp.h\"\n#include \"Sonicteam/RenderAction/SetStencilWriteMask.h\"\n#include \"Sonicteam/RenderAction/SetTexture.h\"\n#include \"Sonicteam/RenderAction/SetUserClipPlaneEnable.h\"\n#include \"Sonicteam/RenderAction/SetViewport.h\"\n#include \"Sonicteam/RenderAction/SetZMode.h\"\n#include \"Sonicteam/RenderPostprocess.h\"\n#include \"Sonicteam/SaveDataTask.h\"\n#include \"Sonicteam/SaveDataTaskXENON.h\"\n#include \"Sonicteam/SelectWindowTask.h\"\n#include \"Sonicteam/SoX/AI/StateMachine.h\"\n#include \"Sonicteam/SoX/ApplicationXenon.h\"\n#include \"Sonicteam/SoX/Audio/Cue.h\"\n#include \"Sonicteam/SoX/Audio/IAudioEngine.h\"\n#include \"Sonicteam/SoX/Audio/Player.h\"\n#include \"Sonicteam/SoX/Audio/PlayerImpl.h\"\n#include \"Sonicteam/SoX/Component.h\"\n#include \"Sonicteam/SoX/Engine/Application.h\"\n#include \"Sonicteam/SoX/Engine/Doc.h\"\n#include \"Sonicteam/SoX/Engine/DocMode.h\"\n#include \"Sonicteam/SoX/Engine/RenderProcess.h\"\n#include \"Sonicteam/SoX/Engine/Task.h\"\n#include \"Sonicteam/SoX/Graphics/Device.h\"\n#include \"Sonicteam/SoX/Graphics/Frame.h\"\n#include \"Sonicteam/SoX/Graphics/FrameGP.h\"\n#include \"Sonicteam/SoX/Graphics/FrameObserver.h\"\n#include \"Sonicteam/SoX/Graphics/Technique.h\"\n#include \"Sonicteam/SoX/Graphics/TechniqueFXL.h\"\n#include \"Sonicteam/SoX/Graphics/Transforms.h\"\n#include \"Sonicteam/SoX/Graphics/Vertex.h\"\n#include \"Sonicteam/SoX/Graphics/Xenon/DeviceXenon.h\"\n#include \"Sonicteam/SoX/Graphics/Xenon/TextureXenon.h\"\n#include \"Sonicteam/SoX/IResource.h\"\n#include \"Sonicteam/SoX/IResource2.h\"\n#include \"Sonicteam/SoX/IResourceMgr.h\"\n#include \"Sonicteam/SoX/Input/Manager.h\"\n#include \"Sonicteam/SoX/LinkNode.h\"\n#include \"Sonicteam/SoX/Math/Matrix.h\"\n#include \"Sonicteam/SoX/Math/Quaternion.h\"\n#include \"Sonicteam/SoX/Math/Vector.h\"\n#include \"Sonicteam/SoX/Message.h\"\n#include \"Sonicteam/SoX/MessageReceiver.h\"\n#include \"Sonicteam/SoX/Object.h\"\n#include \"Sonicteam/SoX/Physics/Entity.h\"\n#include \"Sonicteam/SoX/Physics/Havok/EntityHavok.h\"\n#include \"Sonicteam/SoX/Physics/Havok/EntityHavokImp.h\"\n#include \"Sonicteam/SoX/Physics/Havok/PhantomHavok.h\"\n#include \"Sonicteam/SoX/Physics/Havok/WorldHavok.h\"\n#include \"Sonicteam/SoX/Physics/IntersectEvent.h\"\n#include \"Sonicteam/SoX/Physics/IntersectListener.h\"\n#include \"Sonicteam/SoX/Physics/Phantom.h\"\n#include \"Sonicteam/SoX/Physics/PhantomListener.h\"\n#include \"Sonicteam/SoX/Physics/Shape.h\"\n#include \"Sonicteam/SoX/Physics/ShapeCastEvent.h\"\n#include \"Sonicteam/SoX/Physics/ShapeCastListener.h\"\n#include \"Sonicteam/SoX/Physics/World.h\"\n#include \"Sonicteam/SoX/RefCountObject.h\"\n#include \"Sonicteam/SoX/RefSharedPointer.h\"\n#include \"Sonicteam/SoX/Scenery/Camera.h\"\n#include \"Sonicteam/SoX/Scenery/CameraEventCallback.h\"\n#include \"Sonicteam/SoX/Scenery/CameraImp.h\"\n#include \"Sonicteam/SoX/Scenery/Drawable.h\"\n#include \"Sonicteam/SoX/Thread.h\"\n#include \"Sonicteam/StdImageFilters/BurnoutBlurFilter.h\"\n#include \"Sonicteam/StdImageFilters/SingleTechniqueFilter.h\"\n#include \"Sonicteam/System/CreateStatic.h\"\n#include \"Sonicteam/System/Diagnostics/Performance.h\"\n#include \"Sonicteam/System/Singleton.h\"\n#include \"Sonicteam/TextBook.h\"\n#include \"Sonicteam/TextBookMgr.h\"\n#include \"Sonicteam/TextCard.h\"\n#include \"Sonicteam/TextEntity.h\"\n#include \"Sonicteam/TextFontPicture.h\"\n#include \"Sonicteam/TextFontPictureMgr.h\"\n#include \"Sonicteam/TitleTask.h\"\n#include \"Sonicteam/VehicleMissileCtrl.h\"\n#include \"Sonicteam/VehicleMissileCtrlAutomatic.h\"\n#include \"Sonicteam/VehicleMissileCtrlSingle.h\"\n#include \"Sonicteam/WorldRenderProcess.h\"\n#include \"Sonicteam/sonicXmaPlayer.h\"\n#include \"boost/smart_ptr/make_shared_object.h\"\n#include \"boost/smart_ptr/shared_ptr.h\"\n#include \"d3dxb.h\"\n#include \"hk330/hkArray.h\"\n#include \"hk330/hkReferencedObject.h\"\n#include \"hk330/hkpBroadPhaseHandle.h\"\n#include \"hk330/hkpCdBody.h\"\n#include \"hk330/hkpCollidable.h\"\n#include \"hk330/hkpCollidableCollidableFilter.h\"\n#include \"hk330/hkpCollisionFilter.h\"\n#include \"hk330/hkpEntity.h\"\n#include \"hk330/hkpLinkedCollidable.h\"\n#include \"hk330/hkpPhantom.h\"\n#include \"hk330/hkpProperty.h\"\n#include \"hk330/hkpRayCollidableFilter.h\"\n#include \"hk330/hkpRayShapeCollectionFilter.h\"\n#include \"hk330/hkpRigidBody.h\"\n#include \"hk330/hkpShape.h\"\n#include \"hk330/hkpShapeCollectionFilter.h\"\n#include \"hk330/hkpTypedBroadPhaseHandle.h\"\n#include \"hk330/hkpWorld.h\"\n#include \"hk330/hkpWorldObject.h\"\n#include \"stdx/string.h\"\n#include \"stdx/vector.h\"\n#include \"stdx/wstring.h\"\n"
  },
  {
    "path": "MarathonRecomp/api/Marathon.inl",
    "content": "#pragma once\n\n#include <cpu/guest_stack_var.h>\n#include <kernel/function.h>\n\nconstexpr float RAD2DEGf = 57.2958f;\nconstexpr float DEG2RADf = 0.0174533f;\n\nconstexpr double RAD2DEG = 57.29578018188477;\nconstexpr double DEG2RAD = 0.01745329238474369;\n\n#define MARATHON_CONCAT2(x, y) x##y\n#define MARATHON_CONCAT(x, y) MARATHON_CONCAT2(x, y)\n\n#define MARATHON_INSERT_PADDING(length) \\\n    uint8_t MARATHON_CONCAT(pad, __LINE__)[length]\n\n#define MARATHON_ASSERT_OFFSETOF(type, field, offset) \\\n    static_assert(offsetof(type, field) == offset)\n\n#define MARATHON_ASSERT_SIZEOF(type, size) \\\n    static_assert(sizeof(type) == size)\n\n#define MARATHON_VIRTUAL_FUNCTION(returnType, virtualIndex, ...) \\\n    GuestToHostFunction<returnType>(*(be<uint32_t>*)(g_memory.Translate(*(be<uint32_t>*)(this) + (4 * virtualIndex))), __VA_ARGS__)\n\nstruct marathon_null_ctor {};\n\ninline std::vector<std::string_view> ParseTextVariables(const char* pVariables)\n{\n    std::vector<std::string_view> result{};\n\n    if (!pVariables)\n        return result;\n\n    auto start = pVariables;\n    auto ptr = pVariables;\n    auto depth = 0;\n\n    while (*ptr)\n    {\n        if (*ptr == '(')\n        {\n            depth++;\n        }\n        else if (*ptr == ')')\n        {\n            depth--;\n        }\n        else if (*ptr == ',' && !depth)\n        {\n            result.emplace_back(start, ptr - start);\n            start = ptr + 1;\n        }\n\n        ++ptr;\n    }\n\n    if (ptr != start)\n        result.emplace_back(start, ptr - start);\n\n    return result;\n}\n\ninline std::vector<std::pair<std::string_view, std::string_view>> MapTextVariables(const char* pVariables)\n{\n    std::vector<std::pair<std::string_view, std::string_view>> result{};\n\n    if (!pVariables)\n        return result;\n\n    auto variables = ParseTextVariables(pVariables);\n\n    for (auto& variable : variables)\n    {\n        auto open = variable.find('(');\n        auto close = variable.find(')');\n\n        if (open != std::string_view::npos && close != std::string_view::npos && close > open)\n        {\n            auto type = variable.substr(0, open);\n            auto value = variable.substr(open + 1, close - open - 1);\n\n            result.emplace_back(type, value);\n        }\n        else\n        {\n            result.emplace_back(variable, std::string_view{});\n        }\n    }\n\n    return result;\n}\n\ninline size_t strlenU16(const uint16_t* str)\n{\n    size_t result = 0;\n    uint16_t c = 0xFFFF;\n\n    while (c != 0)\n    {\n        c = str[result];\n        result++;\n    }\n\n    return result;\n}\n\ninline bool strcmpU16(const uint16_t* a, const uint16_t* b, bool endianSwapA = false, bool endianSwapB = false)\n{\n    for (size_t i = 0; i < strlenU16(a); i += 2)\n    {\n        auto c1 = endianSwapA ? ByteSwap(a[i]) : a[i];\n        auto c2 = endianSwapB ? ByteSwap(b[i]) : b[i];\n\n        if (c1 != 0 && c2 == 0)\n            return false;\n\n        if (c1 == 0 && c2 != 0)\n            return false;\n\n        if (c1 != c2)\n            return false;\n    }\n\n    return true;\n}\n\ninline void printU16(const uint16_t* str, bool endianSwap = false)\n{\n    for (size_t i = 0; i < strlenU16(str); i++)\n    {\n        auto c0 = endianSwap ? ByteSwap(str[i]) >> 8 : str[i] >> 8;\n        auto c1 = endianSwap ? ByteSwap(str[i]) & 0xFF : str[i] & 0xFF;\n\n        printf(\"%c%c\", c0, c1);\n    }\n\n    printf(\"\\n\");\n}\n"
  },
  {
    "path": "MarathonRecomp/api/README.md",
    "content": "# Marathon\n\n## Contribution Guide\n\n### Naming Conventions\n\n- Use `camelCase` for local variables, `SNAKE_CASE` for preprocessor macros, and `PascalCase` for everything else. Marathon-specific types that don't exist in the game should use `snake_case` for better differentiation.\n- Class members should be prefixed with `m_`, e.g., `m_Time`. Do not use this prefix for struct members.\n- Pointers should be prefixed with `p`, e.g., `pSonicContext`.\n- Shared pointers should be prefixed with `sp`, e.g., `spDatabase`.\n- References should be prefixed with `r`, e.g., `rMessage`.\n- Static members outside a class should be prefixed with `g_`, e.g., `g_AllocationTracker`.\n- Marathon-specific preprocessor macros should start with `MARATHON_`, e.g., `MARATHON_INSERT_PADDING`.\n- Function pointers should be prefixed with `fp`, e.g., `fpCGameObjectConstructor`.\n\nCombine prefixes as necessary, e.g., `m_sp` for a shared pointer as a class member.\n\n### Coding Style\n\n- Always place curly brackets on a new line.\n- Prefer forward declaring types over including their respective headers.\n- Use <> includes relative to the project's root directory path.\n- Use C++17's nested namespace feature instead of defining multiple namespaces on separate lines.\n- Enum classes are prohibited as they were not available when the game was developed.\n- Avoid placing function definitions in .h files, instead, implement functions in the header's respective .inl file, similar to a .cpp file.\n- Ensure that all class members are declared as public. Even if you suspect that a class member was private in the original code, having it public is more convenient in a modding API.\n- Avoid placing multiple class definitions in a single header file unless you have a good reason to do so.\n- Keep function pointers or addresses outside functions, define them as global variables in the corresponding .inl file. Mark these global variables as `inline` and never nest them within class definitions. You do not need to use the `g_` prefix for function pointers, `fp` is sufficient.\n- Use primitive types defined in `cstdint` instead of using types that come with the language, e.g., use `uint32_t` instead of `unsigned int`. Using `float`, `double` and `bool` is okay.\n\n### Mapping Rules\n\n- Always include the corresponding `offsetof`/`sizeof` assertions for mapped classes/structs. If you are uncertain about the type's size, you can omit the `sizeof` assertion.\n- Use the exact type name from the game if it's available through RTTI, otherwise, you can look for shared pointers that may reveal the original type name.\n- If you are unsure about the name of a class/struct member, use `Field` followed by the hexadecimal byte offset (e.g., `m_Field194`). Avoid names like `m_StoresThisThingMaybe`, you can write comments next to the definition for speculations.\n- If a portion of the byte range is irrelevant to your research or not mapped yet, use the `MARATHON_INSERT_PADDING` macro to align class/struct members correctly.\n- When the class has a virtual function table, if you don't want to map every function in it, you can map only the virtual destructor.\n- The original file locations are likely available in the executable file as assertion file paths. If you cannot find the file path, use your intuition to place the file in a sensible place."
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Actor.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Task.h>\n\nnamespace Sonicteam\n{\n    class Actor : public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(8); // boost::weak_ptr<Sonicteam::GameImp> GameImp;\n        be<uint32_t> m_ActorID;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Actor, m_ActorID, 0x54);\n    MARATHON_ASSERT_SIZEOF(Actor, 0x58);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/ActorManager.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Actor.h>\n\nnamespace Sonicteam\n{\n    class ActorManager\n    {\n    public:\n\t\tbe<uint32_t> m_aActorIDs[0xFFFF];\n\t\txpointer<Actor> m_aActors[0xFFFF];\n\t\tbe<uint32_t> m_LastActorID;\n\t\tbe<uint32_t> m_LastActorIndex;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ActorManager, m_aActorIDs, 0x00);\n    MARATHON_ASSERT_OFFSETOF(ActorManager, m_aActors, 0x3FFFC);\n    MARATHON_ASSERT_OFFSETOF(ActorManager, m_LastActorID, 0x7FFF8);\n    MARATHON_ASSERT_OFFSETOF(ActorManager, m_LastActorIndex, 0x7FFFC);\n    MARATHON_ASSERT_SIZEOF(ActorManager, 0x80004);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/AlertWindowTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/SelectWindowTask.h>\n\nnamespace Sonicteam\n{\n    class AlertWindowTask : public SoX::Engine::Task\n    {\n    public:\n        be<uint32_t> m_HasSelectWindow;\n        MARATHON_INSERT_PADDING(0x2C);\n        be<uint32_t> m_Operation;\n        MARATHON_INSERT_PADDING(0x14);\n        boost::anonymous_shared_ptr m_aspOptions[3];\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> m_OptionCount;\n        xpointer<SelectWindowTask> m_pSelectWindowTask;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(AlertWindowTask, m_HasSelectWindow, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(AlertWindowTask, m_Operation, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(AlertWindowTask, m_aspOptions, 0x94);\n    MARATHON_ASSERT_OFFSETOF(AlertWindowTask, m_OptionCount, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(AlertWindowTask, m_pSelectWindowTask, 0xB4);\n    MARATHON_ASSERT_SIZEOF(AlertWindowTask, 0xB8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/AppMarathon.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/ApplicationXenon.h>\n#include <Sonicteam/DocMarathonState.h>\n#include <Sonicteam/GameMode.h>\n\nnamespace Sonicteam\n{\n    class AppMarathon : public SoX::ApplicationXenon\n    {\n    public:\n        xpointer<DocMarathonState> m_pDoc;\n\n        static AppMarathon* GetInstance();\n\n        GameImp* GetGame() const\n        {\n            if (auto pGameMode = m_pDoc->GetDocMode<Sonicteam::GameMode>())\n                return pGameMode->GetGame();\n\n            return nullptr;\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(AppMarathon, m_pDoc, 0x180);\n}\n\n#include <Sonicteam/AppMarathon.inl>\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/AppMarathon.inl",
    "content": "namespace Sonicteam\n{\n    inline AppMarathon* AppMarathon::GetInstance()\n    {\n        return *(xpointer<AppMarathon>*)MmGetHostAddress(0x82D3B348);\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/AudioEngineXenon.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Doc.h>\n#include <Sonicteam/SoX/Audio/IAudioEngine.h>\n#include <Sonicteam/System/CreateStatic.h>\n#include <Sonicteam/System/Singleton.h>\n\nnamespace Sonicteam\n{\n    class AudioEngineXenon : public SoX::Audio::IAudioEngine, public System::Singleton<AudioEngineXenon, 0x82D37AC8, System::CreateStatic<AudioEngineXenon, 0x824A5C78>>\n    {\n    public:\n        MARATHON_INSERT_PADDING(8);\n        be<float> m_MusicVolume;\n        be<float> m_EffectsVolume;\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(AudioEngineXenon, m_MusicVolume, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(AudioEngineXenon, m_EffectsVolume, 0x10);\n    MARATHON_ASSERT_SIZEOF(AudioEngineXenon, 0x2C);\n}   \n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/ButtonWindowTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/HUDButtonWindow.h>\n\nnamespace Sonicteam\n{\n    class ButtonWindowTask : public SoX::Engine::Task\n    {\n    public:\n        xpointer<HUDButtonWindow> m_pHUDButtonWindow;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ButtonWindowTask, m_pHUDButtonWindow, 0x4C);\n    MARATHON_ASSERT_SIZEOF(ButtonWindowTask, 0x58);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/CObjBalloonIconDrawable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Scenery/Drawable.h>\n#include <Sonicteam/MyGraphicsDevice.h>\n\nnamespace Sonicteam\n{\n    class CObjBalloonIconDrawable : public SoX::Scenery::Drawable\n    {\n    public:\n        xpointer<MyGraphicsDevice> m_pMyGraphicsDevice;\n        MARATHON_INSERT_PADDING(0x24);\n        SoX::Graphics::Vertex m_aVertices[4]; // BL, TL, BR, TR\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CObjBalloonIconDrawable, m_pMyGraphicsDevice, 0x70);\n    MARATHON_ASSERT_OFFSETOF(CObjBalloonIconDrawable, m_aVertices, 0x98);\n    MARATHON_ASSERT_SIZEOF(CObjBalloonIconDrawable, 0x1A0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Camera/CameraMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/SoX/MessageReceiver.h>\n#include <Sonicteam/GameImp.h>\n\nnamespace Sonicteam::Camera\n{\n    class CameraMode : public SoX::MessageReceiver\n    {\n    public:\n        boost::shared_ptr<Game> m_spGame;\n        xpointer<void> m_pCameraInputListener;\n        MARATHON_INSERT_PADDING(0x18);\n\n        GameImp* GetGame() const\n        {\n            return (GameImp*)m_spGame.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CameraMode, m_spGame, 0x04);\n    MARATHON_ASSERT_OFFSETOF(CameraMode, m_pCameraInputListener, 0x0C);\n    MARATHON_ASSERT_SIZEOF(CameraMode, 0x28);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Camera/CameraModeManager.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/Camera/Cameraman.h>\n#include <Sonicteam/Camera/CameraMode.h>\n\nnamespace Sonicteam::Camera\n{\n    class CameraModeManager\n    {\n    public:\n        // TODO: research these fields (processed by 0x8218C100).\n        struct UnknownStruct\n        {\n            MARATHON_INSERT_PADDING(0x14);\n        };\n\n        xpointer<Cameraman> m_pCameraman;\n        boost::shared_ptr<CameraMode> m_spCameraMode;\n        MARATHON_INSERT_PADDING(8);\n        stdx::vector<UnknownStruct> m_vUnkStructs;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CameraModeManager, m_pCameraman, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CameraModeManager, m_spCameraMode, 0x04);\n    MARATHON_ASSERT_OFFSETOF(CameraModeManager, m_vUnkStructs, 0x14);\n    MARATHON_ASSERT_SIZEOF(CameraModeManager, 0x28);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Camera/Cameraman.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n\nnamespace Sonicteam::Camera\n{\n    class CameraModeManager;\n\n    class Cameraman : public Actor\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x24);\n        boost::shared_ptr<CameraModeManager> m_spCameraModeManager;\n        MARATHON_INSERT_PADDING(0x2C);\n        be<float> m_FOV;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Cameraman, m_spCameraModeManager, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(Cameraman, m_FOV, 0xB0);\n    MARATHON_ASSERT_SIZEOF(Cameraman, 0xC0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Camera/SonicCamera.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Camera\n{\n    class SonicCamera : public CameraMode\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        xpointer<DocMarathonState> m_pDoc;\n        MARATHON_INSERT_PADDING(0x14);\n        be<float> m_SpringK;\n        be<float> m_DampingK;\n        be<float> m_AzDamping;\n        be<float> m_AltDamping;\n        be<float> m_AzDriveK;\n        be<float> m_AzDampingK;\n        be<float> m_AltDriveK;\n        be<float> m_AltDampingK;\n        MARATHON_INSERT_PADDING(0x3C);\n        be<float> m_FovY;\n        MARATHON_INSERT_PADDING(0x3C);\n        be<float> m_Distance;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_pDoc, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_SpringK, 0x44);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_DampingK, 0x48);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_AzDamping, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_AltDamping, 0x50);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_AzDriveK, 0x54);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_AzDampingK, 0x58);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_AltDriveK, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_AltDampingK, 0x60);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_FovY, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(SonicCamera, m_Distance, 0xE0);\n    MARATHON_ASSERT_SIZEOF(SonicCamera, 0xF0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/CommonObjectHint.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/PropFixture.h>\n\nnamespace Sonicteam\n{\n    class CommonObjectHint : public PropFixture\n    {\n    public:\n        enum CommonObjectHintType : uint32_t\n        {\n            CommonObjectHintType_HintRing,\n            CommonObjectHintType_HintVolume\n        };\n\n        MARATHON_INSERT_PADDING(4);\n        char m_MessageName[20];\n        MARATHON_INSERT_PADDING(0x40);\n        be<CommonObjectHintType> m_Type;\n        bool m_IsFastAnim;\n        bool m_IsHitAnim;\n        MARATHON_INSERT_PADDING(2);\n        be<float> m_HitTime;\n        MARATHON_INSERT_PADDING(0x10);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CommonObjectHint, m_MessageName, 0x184);\n    MARATHON_ASSERT_OFFSETOF(CommonObjectHint, m_Type, 0x1D8);\n    MARATHON_ASSERT_OFFSETOF(CommonObjectHint, m_IsFastAnim, 0x1DC);\n    MARATHON_ASSERT_OFFSETOF(CommonObjectHint, m_IsHitAnim, 0x1DD);\n    MARATHON_ASSERT_OFFSETOF(CommonObjectHint, m_HitTime, 0x1E0);\n    MARATHON_ASSERT_SIZEOF(CommonObjectHint, 0x200);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/CsdLink.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class CsdLink\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            MARATHON_INSERT_PADDING(4);\n            be<uint32_t> fpUpdate;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        xpointer<CsdLink> m_pNext;\n        xpointer<CsdLink> m_pPrevious;\n        be<float> m_Priority;\n        MARATHON_INSERT_PADDING(8);\n\n        void* Destroy(uint32_t flags = 1)\n        {\n            return GuestToHostFunction<void*>(m_pVftable->fpDestroy.get(), this, flags);\n        }\n\n        int Update(double deltaTime = 0.0)\n        {\n            return GuestToHostFunction<int>(m_pVftable->fpUpdate.get(), this, deltaTime);\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CsdLink, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(CsdLink, m_pNext, 0x04);\n    MARATHON_ASSERT_OFFSETOF(CsdLink, m_pPrevious, 0x08);\n    MARATHON_ASSERT_OFFSETOF(CsdLink, m_Priority, 0x0C);\n    MARATHON_ASSERT_SIZEOF(CsdLink, 0x18);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/CsdManager.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/IResourceMgr.h>\n#include <Sonicteam/System/CreateStatic.h>\n#include <Sonicteam/System/Singleton.h>\n\nnamespace Sonicteam\n{\n    class CsdManager : public SoX::IResourceMgr, public System::Singleton<CsdManager, 0x82D3BC58, System::CreateStatic<CsdManager, 0x825E9530>>\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1C);\n    };\n\n    MARATHON_ASSERT_SIZEOF(CsdManager, 0x2C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/CsdObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/CsdLink.h>\n#include <Sonicteam/CsdResource.h>\n\nnamespace Sonicteam\n{\n    class CsdObject : public SoX::RefCountObject, public CsdLink\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        xpointer<Chao::CSD::CProject> m_pCsdProject;\n        xpointer<CsdResource> m_pCsdResource;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CsdObject, m_pCsdProject, 0x24);\n    MARATHON_ASSERT_OFFSETOF(CsdObject, m_pCsdResource, 0x28);\n    MARATHON_ASSERT_SIZEOF(CsdObject, 0x2C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/CsdResource.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/IResource2.h>\n#include <Sonicteam/CsdManager.h>\n\nnamespace Sonicteam\n{\n    class CsdResource : public SoX::IResource2<CsdResource, CsdManager>\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(CsdResource, 0x68);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/DocMarathonImp.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Doc.h>\n#include <Sonicteam/SoX/Input/Manager.h>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <api/stdx/vector.h>\n\nnamespace Sonicteam\n{\n    class DocMarathonImp : public SoX::Engine::Doc\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x40);\n        stdx::vector<boost::shared_ptr<SoX::Input::Manager>> m_vspInputManager;\n        MARATHON_INSERT_PADDING(0x24);\n        bool m_VFrame;\n        MARATHON_INSERT_PADDING(0x55B58);\n        be<uint32_t> m_aPadIDs[4];\n        MARATHON_INSERT_PADDING(0x2C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_vspInputManager, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_VFrame, 0xD0);\n    MARATHON_ASSERT_OFFSETOF(DocMarathonImp, m_aPadIDs, 0x55C2C);\n    MARATHON_ASSERT_SIZEOF(DocMarathonImp, 0x55C68);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/DocMarathonState.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/DocMarathonImp.h>\n\nnamespace Sonicteam\n{\n    class DocMarathonState : public DocMarathonImp {};\n\n    MARATHON_ASSERT_SIZEOF(DocMarathonState, 0x55C68);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Enemy/EnemyShot.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Enemy\n{\n    class EnemyShot : public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1C);\n    };\n\n    MARATHON_ASSERT_SIZEOF(EnemyShot, 0x68);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Enemy/EnemyShotNormal.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Enemy/EnemyShotPoint.h>\n\nnamespace Sonicteam::Enemy\n{\n    class EnemyShotNormal : public EnemyShotPoint {};\n\n    MARATHON_ASSERT_SIZEOF(EnemyShotNormal, 0x160);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Enemy/EnemyShotPoint.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyPhantom.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n\nnamespace Sonicteam::Enemy\n{\n    class EnemyShotPoint : public EnemyShot\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x20);\n        SoX::RefSharedPointer<MyPhantom> m_spPhantom;\n        MARATHON_INSERT_PADDING(0xD4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(EnemyShotPoint, m_spPhantom, 0x88);\n    MARATHON_ASSERT_SIZEOF(EnemyShotPoint, 0x160);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Fixture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Actor.h>\n#include <Sonicteam/SoX/Math/Quaternion.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n\nnamespace Sonicteam\n{\n    class Fixture : public Actor\n    {\n    public:\n        SoX::Math::Quaternion m_Rotation;\n        SoX::Math::Vector m_Position;\n        MARATHON_INSERT_PADDING(0xF0);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Fixture, m_Rotation, 0x60);\n    MARATHON_ASSERT_OFFSETOF(Fixture, m_Position, 0x70);\n    MARATHON_ASSERT_SIZEOF(Fixture, 0x170);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Game.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class Game : public SoX::MessageReceiver {};\n\n    MARATHON_ASSERT_SIZEOF(Game, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/GameImp.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/Mission/Core.h>\n#include <Sonicteam/SoX/Audio/Cue.h>\n#include <Sonicteam/SoX/Physics/World.h>\n#include <Sonicteam/SoX/Scenery/Camera.h>\n#include <Sonicteam/SoX/Scenery/CameraImp.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/Game.h>\n#include <Sonicteam/TextBook.h>\n#include <stdx/vector.h>\n\nnamespace Sonicteam\n{\n    class ActorManager;\n    class GameScript;\n\n    class GameImp : public Game\n    {\n    public:\n        enum GameState : uint32_t\n        {\n            GameState_MainMenu,\n            GameState_Stage,\n            GameState_Event,\n            GameState_Movie,\n            GameState_Result,\n            GameState_Message,\n            GameState_6,\n            GameState_Save,\n            GameState_ReturnToMainMenu\n        };\n\n        enum GameFlags : uint32_t\n        {\n            GameFlags_RestartArea1 = 1,\n            GameFlags_RestartArea2 = 0x200,\n            GameFlags_IsPaused = 0x1000,\n            GameFlags_LoadArea1 = 0x40000,\n            GameFlags_LoadArea2 = 0x200000\n        };\n\n        struct PlayerData\n        {\n            be<uint32_t> ActorID;\n            be<uint32_t> RingCount;\n            MARATHON_INSERT_PADDING(4);\n            be<uint32_t> LifeCount;\n            be<uint32_t> ScoreCount;\n            be<float> AliveTime;\n            be<float> Time;\n            MARATHON_INSERT_PADDING(4);\n            be<float> SectionTime;\n            be<float> GaugeValue;\n            be<uint32_t> MaturityLevel;\n            be<float> MaturityValue;\n            MARATHON_INSERT_PADDING(4);\n            be<uint32_t> ExtendRingCount;\n            be<uint32_t> GemIndex;\n            MARATHON_INSERT_PADDING(0x10);\n        };\n\n        MARATHON_INSERT_PADDING(4);\n        be<GameState> m_State;\n        xpointer<DocMarathonState> m_pDoc;\n        be<GameFlags> m_Flags;\n        MARATHON_INSERT_PADDING(0xE2C);\n        PlayerData m_PlayerData[4];\n        MARATHON_INSERT_PADDING(0x200);\n        bool m_IsStage;\n        MARATHON_INSERT_PADDING(0x0C);\n        be<uint32_t> m_Field1180;\n        xpointer<GameScript> m_pGameScript;\n        be<uint32_t> m_aObjPlayerActorID[0x0F];\n        boost::shared_ptr<ActorManager> m_spActorManager;\n        xpointer<TextBook> m_pSystemTextBook;\n        MARATHON_INSERT_PADDING(8);\n        stdx::vector<stdx::vector<boost::shared_ptr<SoX::Scenery::Camera>>> m_vvspCameras;\n        MARATHON_INSERT_PADDING(0x1B4);\n        xpointer<SoX::Audio::Cue> m_pBgmCue;\n        MARATHON_INSERT_PADDING(0x36C);\n        xpointer<TextBook> m_pHintTextBook;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<Mission::Core> m_pMissionCore;\n        MARATHON_INSERT_PADDING(0x2A4);\n        SoX::RefSharedPointer<SoX::Physics::World> m_spPhysicsWorld;\n        xpointer<void> m_pMyCollisionFilter;\n        MARATHON_INSERT_PADDING(0x0C);\n\n        int PlayerActorIDToIndex(int32_t actorId) const\n        {\n            for (int i = 0; i < 4; i++)\n            {\n                if (m_PlayerData[i].ActorID == actorId)\n                    return i;\n            }\n\n            return -1;\n        }\n\n        SoX::Scenery::CameraImp* GetCamera(const char* pName, int which = 0)\n        {\n            if (m_vvspCameras.empty())\n                return nullptr;\n\n            for (auto& spCamera : m_vvspCameras[which])\n            {\n                auto pCameraImp = (SoX::Scenery::CameraImp*)spCamera.get();\n\n                if (pCameraImp->m_Name == pName)\n                    return pCameraImp;\n            }\n\n            return nullptr;\n        }\n\n        template <typename T = SoX::Audio::Cue>\n        T* GetBgmCue() const\n        {\n            return (T*)m_pBgmCue.get();\n        }\n\n        template <typename T = SoX::Physics::World>\n        T* GetPhysicsWorld() const\n        {\n            return (T*)m_spPhysicsWorld.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, ActorID, 0x00);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, RingCount, 0x04);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, LifeCount, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, ScoreCount, 0x10);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, AliveTime, 0x14);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, Time, 0x18);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, SectionTime, 0x20);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, GaugeValue, 0x24);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, MaturityLevel, 0x28);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, MaturityValue, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, ExtendRingCount, 0x34);\n    MARATHON_ASSERT_OFFSETOF(GameImp::PlayerData, GemIndex, 0x38);\n    MARATHON_ASSERT_SIZEOF(GameImp::PlayerData, 0x4C);\n\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_State, 0x08);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pDoc, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_Flags, 0x10);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_PlayerData, 0xE40);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_IsStage, 0x1170);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_Field1180, 0x1180);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pGameScript, 0x1184);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_aObjPlayerActorID, 0x1188);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_spActorManager, 0x11C4);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pSystemTextBook, 0x11CC);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_vvspCameras, 0x11D8);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pBgmCue, 0x139C);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pHintTextBook, 0x170C);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pMissionCore, 0x1714);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_spPhysicsWorld, 0x19BC);\n    MARATHON_ASSERT_OFFSETOF(GameImp, m_pMyCollisionFilter, 0x19C0);\n    MARATHON_ASSERT_SIZEOF(GameImp, 0x19D0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/GameMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/GameImp.h>\n\nnamespace Sonicteam\n{\n    class GameMode : public SoX::Engine::DocMode\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1C);\n        xpointer<Game> m_pGame;\n        MARATHON_INSERT_PADDING(0x1C);\n\n        GameImp* GetGame() const\n        {\n            return (GameImp*)m_pGame.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(GameMode, m_pGame, 0x6C);\n    MARATHON_ASSERT_SIZEOF(GameMode, 0x8C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Globals.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    enum Character : uint32_t\n    {\n        Character_Sonic,\n        Character_Shadow,\n        Character_Silver,\n        Character_Tails,\n        Character_Amy,\n        Character_Knuckles,\n        Character_Omega,\n        Character_Rouge,\n        Character_Blaze\n    };\n\n    struct Globals\n    {\n        static inline be<float>* ms_MainDisplayColours[9];\n\n        static void Init()\n        {\n            for (int i = 0; i < 9; i++)\n                ms_MainDisplayColours[i] = (be<float>*)MmGetHostAddress(0x82036BE4 + (i * 4));\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDButtonWindow.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Chao/CSD/Manager/csdmSceneObserver.h>\n#include <Sonicteam/SoX/Engine/Task.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/CsdResource.h>\n#include <Sonicteam/HudTextParts.h>\n#include <Sonicteam/TextBook.h>\n#include <Sonicteam/TextCard.h>\n#include <Sonicteam/TextEntity.h>\n\nnamespace Sonicteam\n{\n    class HUDButtonWindow;\n\n    class HUDButtonWindowNextObserver : public SoX::RefCountObject, public Chao::CSD::CSceneObserver\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        xpointer<HUDButtonWindow> m_pHUDButtonWindow;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    class HUDButtonWindow : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        xpointer<TextBook> m_pTextBook;\n        xpointer<CsdResource> m_pCsdResource;\n        HUDButtonWindowNextObserver m_NextObserver;\n        be<float> m_Field80;\n        boost::shared_ptr<TextCard> m_spTextCard;\n        boost::shared_ptr<TextEntity> m_spTextEntity;\n        xpointer<HudTextParts> m_pHudTextParts;\n        be<float> m_Field98;\n        bool m_IsIntroAnimStarted;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindowNextObserver, m_pHUDButtonWindow, 0x1C);\n    MARATHON_ASSERT_SIZEOF(HUDButtonWindowNextObserver, 0x24);\n\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_pTextBook, 0x54);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_pCsdResource, 0x58);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_NextObserver, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_Field80, 0x80);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_spTextCard, 0x84);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_spTextEntity, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_pHudTextParts, 0x94);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_Field98, 0x98);\n    MARATHON_ASSERT_OFFSETOF(HUDButtonWindow, m_IsIntroAnimStarted, 0x9C);\n    MARATHON_ASSERT_SIZEOF(HUDButtonWindow, 0xA0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDCALLBACK.h",
    "content": "#pragma once\n\nnamespace Sonicteam\n{\n    class HUDCALLBACK\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDCALLBACK, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(HUDCALLBACK, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDGoldMedal.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/SoX/Engine/Doc.h>\n#include <Sonicteam/SoX/Engine/Task.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/CsdObject.h>\n#include <Sonicteam/HudTextParts.h>\n#include <Sonicteam/TextCard.h>\n#include <Sonicteam/TextEntity.h>\n\nnamespace Sonicteam\n{\n    class HUDGoldMedal : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        xpointer<CsdObject> m_pCsdObject;\n        xpointer<HudTextParts> m_pMedalCountText;\n        xpointer<SoX::Engine::Doc> m_pDoc;\n        MARATHON_INSERT_PADDING(0x3C);\n        be<uint32_t> m_EpisodeIndex;\n        be<uint32_t> m_ScrollIndex;\n        be<uint32_t> m_MedalCount;\n        boost::shared_ptr<TextCard> m_spMedalCountTextCard;\n        MARATHON_INSERT_PADDING(0x10);\n        boost::shared_ptr<TextCard> m_aspTextCards[5];\n        boost::shared_ptr<TextEntity> m_aspTextEntities[5];\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_pCsdObject, 0x54);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_pMedalCountText, 0x58);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_pDoc, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_EpisodeIndex, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_ScrollIndex, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_MedalCount, 0xA4);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_spMedalCountTextCard, 0xA8);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_aspTextCards, 0xC0);\n    MARATHON_ASSERT_OFFSETOF(HUDGoldMedal, m_aspTextEntities, 0xE8);\n    MARATHON_ASSERT_SIZEOF(HUDGoldMedal, 0x110);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDLimitTime.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class HUDLimitTime : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        static constexpr float ms_AlertThreshold = 10.0f;\n\n        xpointer<CsdObject> m_pCsdObject;\n        be<float> m_X;\n        be<float> m_Y;\n        MARATHON_INSERT_PADDING(0x30);\n        be<float> m_Time;\n        bool m_IsAboveAlertThreshold;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDLimitTime, m_pCsdObject, 0x54);\n    MARATHON_ASSERT_OFFSETOF(HUDLimitTime, m_X, 0x58);\n    MARATHON_ASSERT_OFFSETOF(HUDLimitTime, m_Y, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(HUDLimitTime, m_Time, 0x90);\n    MARATHON_ASSERT_OFFSETOF(HUDLimitTime, m_IsAboveAlertThreshold, 0x94);\n    MARATHON_ASSERT_SIZEOF(HUDLimitTime, 0x98);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDLoading.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class HUDLoading : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        enum HUDLoadingFlags\n        {\n            HUDLoadingFlags_Finished = 6,\n            HUDLoadingFlags_Open = 0x200,\n            HUDLoadingFlags_End = 0x400\n        };\n\n        MARATHON_INSERT_PADDING(0x5C);\n        be<uint32_t> m_Flags;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDLoading, m_Flags, 0xB0);\n    MARATHON_ASSERT_SIZEOF(HUDLoading, 0xBC);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDMainDisplay.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class HUDMainDisplay : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x24);\n        be<Character> m_Character;\n        MARATHON_INSERT_PADDING(0x108);\n        boost::shared_ptr<TextEntity> m_Field184;\n        boost::shared_ptr<TextEntity> m_spTrickPointText;\n        boost::shared_ptr<TextEntity> m_Field194;\n        MARATHON_INSERT_PADDING(0x10);\n        boost::shared_ptr<TextEntity> m_spSavePointTimeText;\n        MARATHON_INSERT_PADDING(0x2C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDMainDisplay, m_Character, 0x78);\n    MARATHON_ASSERT_OFFSETOF(HUDMainDisplay, m_Field184, 0x184);\n    MARATHON_ASSERT_OFFSETOF(HUDMainDisplay, m_spTrickPointText, 0x18C);\n    MARATHON_ASSERT_OFFSETOF(HUDMainDisplay, m_Field194, 0x194);\n    MARATHON_ASSERT_OFFSETOF(HUDMainDisplay, m_spSavePointTimeText, 0x1AC);\n    MARATHON_ASSERT_SIZEOF(HUDMainDisplay, 0x1E0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDMainMenu.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/CsdObject.h>\n#include <Sonicteam/HudTextParts.h>\n\nnamespace Sonicteam\n{\n    class HUDMainMenu : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        enum HUDMainMenuState : uint32_t\n        {\n            HUDMainMenuState_OptionsOutro = 5,\n            HUDMainMenuState_SinglePlayerTextOutro = 8,\n            HUDMainMenuState_SinglePlayerCursorIntro = 86,\n            HUDMainMenuState_OptionsIntro = 90,\n            HUDMainMenuState_MainCursorIntro = 98,\n            HUDMainMenuState_MainCursorOutro = 99,\n            HUDMainMenuState_GoldMedalResultsCursorOutro = 101\n        };\n\n        MARATHON_INSERT_PADDING(0x20);\n        xpointer<CsdObject> m_pCsdObject;\n        MARATHON_INSERT_PADDING(0x1B0);\n        be<uint32_t> m_CursorFlags;\n        MARATHON_INSERT_PADDING(0x32C);\n        xpointer<HudTextParts> m_pHudTextRoot;\n        MARATHON_INSERT_PADDING(0x48C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDMainMenu, m_pCsdObject, 0x74);\n    MARATHON_ASSERT_OFFSETOF(HUDMainMenu, m_CursorFlags, 0x228);\n    MARATHON_ASSERT_OFFSETOF(HUDMainMenu, m_pHudTextRoot, 0x558);\n    MARATHON_ASSERT_SIZEOF(HUDMainMenu, 0x9E8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDMessageWindow.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/HintWindowTask.h>\n\nnamespace Sonicteam\n{\n    class HUDMessageWindow : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        xpointer<CsdObject> m_pCsdObject;\n        MARATHON_INSERT_PADDING(0x1C);\n        xpointer<HUDMessageWindow> m_pParent;\n        MARATHON_INSERT_PADDING(0x14);\n        xpointer<HintWindowTask> m_pHintWindowTask;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDMessageWindow, m_pCsdObject, 0x54);\n    MARATHON_ASSERT_OFFSETOF(HUDMessageWindow, m_pParent, 0x74);\n    MARATHON_ASSERT_OFFSETOF(HUDMessageWindow, m_pHintWindowTask, 0x8C);\n    MARATHON_ASSERT_SIZEOF(HUDMessageWindow, 0x90);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDOption.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/HudTextParts.h>\n\nnamespace Sonicteam\n{\n    class HUDOption : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x188);\n        xpointer<HudTextParts> m_pHudTextRoot;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDOption, m_pHudTextRoot, 0x1DC);\n    MARATHON_ASSERT_SIZEOF(HUDOption, 0x1E8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDPopupScreen.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/TechniqueFXL.h>\n#include <Sonicteam/MyTexture.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class HUDPopupScreen : public SoX::Engine::Task\n    {\n    public:\n        enum HUDPopupScreenState : uint32_t\n        {\n            HUDPopupScreenState_Opening = 1,\n            HUDPopupScreenState_Idle,\n            HUDPopupScreenState_Closing\n        };\n\n        xpointer<CsdObject> m_pCsdObject;\n        xpointer<MyTexture> m_pMainTexture;\n        xpointer<MyTexture> m_pMaskTexture;\n        xpointer<SoX::Graphics::TechniqueFXL> m_pTechnique;\n        stdx::string m_SceneName;\n        stdx::string m_SpriteName;\n        be<HUDPopupScreenState> m_State;\n        MARATHON_INSERT_PADDING(1);\n        bool m_IsClosing;\n        MARATHON_INSERT_PADDING(2);\n        be<float> m_ClosingTime;\n        be<float> m_X;\n        be<float> m_Y;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_pCsdObject, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_pMainTexture, 0x50);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_pMaskTexture, 0x54);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_pTechnique, 0x58);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_SceneName, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_SpriteName, 0x78);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_State, 0x94);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_IsClosing, 0x99);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_ClosingTime, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_X, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(HUDPopupScreen, m_Y, 0xA4);\n    MARATHON_ASSERT_SIZEOF(HUDPopupScreen, 0xB0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDRaderMap.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class HUDRaderMap : public HUDPopupScreen {};\n\n    MARATHON_ASSERT_SIZEOF(HUDRaderMap, 0xB0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HUDStageTitle.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class HUDStageTitle : public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(8);\n        xpointer<HudTextParts> m_pHudTextParts;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HUDStageTitle, m_pHudTextParts, 0x54);\n    MARATHON_ASSERT_SIZEOF(HUDStageTitle, 0x58);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HintWindowTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class HintWindowTask : public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x40);\n        boost::shared_ptr<TextEntity> m_Field8C;\n        boost::shared_ptr<TextEntity> m_Field94;\n        MARATHON_INSERT_PADDING(0x60);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HintWindowTask, m_Field8C, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(HintWindowTask, m_Field94, 0x94);\n    MARATHON_ASSERT_SIZEOF(HintWindowTask, 0xFC);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/HudTextParts.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/CsdObject.h>\n#include <Sonicteam/TextCard.h>\n#include <Sonicteam/TextEntity.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class HudTextParts : public SoX::RefCountObject\n    {\n    public:\n        xpointer<HudTextParts> m_pNext;\n        xpointer<DocMarathonState> m_pDoc;\n        boost::shared_ptr<TextCard> m_spTextCard;\n        boost::shared_ptr<TextEntity> m_spTextEntity;\n        xpointer<CsdObject> m_pCsdObject;\n        stdx::string m_SceneName;\n        stdx::string m_CastName;\n        be<float> m_Priority;\n        be<float> m_OffsetX;\n        be<float> m_OffsetY;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> m_AlignmentFlags;\n\n        HudTextParts* Find(const char* pSceneName)\n        {\n            auto pRoot = this;\n\n            while (pRoot)\n            {\n                if (pRoot->m_SceneName == pSceneName)\n                    return pRoot;\n\n                pRoot = pRoot->m_pNext;\n            }\n\n            return nullptr;\n        }\n\n        HudTextParts* Find(const char* pSceneName, const char* pCastName)\n        {\n            auto pRoot = this;\n\n            while (pRoot)\n            {\n                if (pRoot->m_SceneName == pSceneName && pRoot->m_CastName == pCastName)\n                    return pRoot;\n\n                pRoot = pRoot->m_pNext;\n            }\n\n            return nullptr;\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_pNext, 0x08);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_pDoc, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_spTextCard, 0x10);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_spTextEntity, 0x18);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_pCsdObject, 0x20);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_SceneName, 0x24);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_CastName, 0x40);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_Priority, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_OffsetX, 0x60);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_OffsetY, 0x64);\n    MARATHON_ASSERT_OFFSETOF(HudTextParts, m_AlignmentFlags, 0x6C);\n    MARATHON_ASSERT_SIZEOF(HudTextParts, 0x70);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/ImageFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class ImageFilter\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ImageFilter, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(ImageFilter, 8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MainDisplayTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class MainDisplayTask : public SoX::Engine::Task\n    {\n    public:\n        xpointer<HUDMainDisplay> m_pHUDMainDisplay;\n        MARATHON_INSERT_PADDING(0x20);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MainDisplayTask, m_pHUDMainDisplay, 0x4C);\n    MARATHON_ASSERT_SIZEOF(MainDisplayTask, 0x70);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MainMenuExpositionTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam\n{\n    class MainMenuExpositionTask : public SoX::RefCountObject, public SoX::Engine::Task\n    {\n    public:\n        be<uint32_t> m_TextMotionState;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MainMenuExpositionTask, m_TextMotionState, 0x54);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MainMenuTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/Player/Object.h>\n#include <Sonicteam/ButtonWindowTask.h>\n#include <Sonicteam/MainMenuExpositionTask.h>\n\nnamespace Sonicteam\n{\n    class MainMenuTask : public SoX::Engine::Task\n    {\n    public:\n        enum MainMenuState : uint32_t\n        {\n            MainMenuState_MainMenuBack = 1,\n            MainMenuState_MainMenu = 2,\n            MainMenuState_ExitPrompt = 4,\n            MainMenuState_SinglePlayer = 6,\n            MainMenuState_EpisodeSelect = 9,\n            MainMenuState_TrialSelect = 0x0D,\n            MainMenuState_SelectCharacter = 0x0F,\n            MainMenuState_ActTrial = 0x11,\n            MainMenuState_TownTrial = 0x15,\n            MainMenuState_Multiplayer = 0x17,\n            MainMenuState_Extras = 0x1C,\n            MainMenuState_Tag = 0x1E,\n            MainMenuState_Tag1PSelect = 0x1F,\n            MainMenuState_Battle = 0x22,\n            MainMenuState_GoldMedalResultsOpen = 0x26,\n            MainMenuState_GoldMedalResults = 0x27,\n            MainMenuState_AudioRoom = 0x2F,\n            MainMenuState_TheaterRoom = 0x31,\n            MainMenuState_Options = 0x33,\n            MainMenuState_ExitToStage = 0x3B,\n            MainMenuState_ExitToTitle = 0x3C\n        };\n\n        be<uint32_t> m_State;\n        MARATHON_INSERT_PADDING(0x24);\n        xpointer<HUDMainMenu> m_pHUDMainMenu;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<HUDGoldMedal> m_pHUDGoldMedal;\n        MARATHON_INSERT_PADDING(0x14);\n        xpointer<ButtonWindowTask> m_pButtonWindowTask;\n        SoX::RefSharedPointer<MainMenuExpositionTask> m_spMainMenuExpositionTask;\n        be<uint32_t> m_MainMenuSelectedIndex;\n        be<uint32_t> m_SinglePlayerSelectedIndex;\n        MARATHON_INSERT_PADDING(0x14);\n        be<uint32_t> m_FieldBC;\n        MARATHON_INSERT_PADDING(0x60);\n        be<uint32_t> m_GoldMedalEpisodeIndex;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> m_GoldMedalScrollIndex;\n        MARATHON_INSERT_PADDING(0x144);\n        be<uint32_t> m_IsChangingState;\n        MARATHON_INSERT_PADDING(8);\n        be<uint32_t> m_PressedButtons;\n        MARATHON_INSERT_PADDING(0x18);\n        xpointer<Actor> m_Field298;\n        xpointer<Player::Object> m_apSelectCharacters[9];\n        MARATHON_INSERT_PADDING(0x38);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_State, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pHUDMainMenu, 0x74);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pHUDGoldMedal, 0x80);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_pButtonWindowTask, 0x98);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_spMainMenuExpositionTask, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_MainMenuSelectedIndex, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_SinglePlayerSelectedIndex, 0xA4);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_FieldBC, 0xBC);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_GoldMedalEpisodeIndex, 0x120);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_GoldMedalScrollIndex, 0x128);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_IsChangingState, 0x270);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_PressedButtons, 0x27C);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_Field298, 0x298);\n    MARATHON_ASSERT_OFFSETOF(MainMenuTask, m_apSelectCharacters, 0x29C);\n    MARATHON_ASSERT_SIZEOF(MainMenuTask, 0x2F8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MainMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/FrameGP.h>\n#include <Sonicteam/SoX/Scenery/Camera.h>\n\nnamespace Sonicteam\n{\n    class MainMode : public SoX::Engine::DocMode\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x24);\n        boost::shared_ptr<SoX::Scenery::Camera> m_spSelectCamera;\n        xpointer<SoX::Graphics::FrameGP> m_pFrameGP;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MainMode, m_spSelectCamera, 0x74);\n    MARATHON_ASSERT_OFFSETOF(MainMode, m_pFrameGP, 0x7C);\n    MARATHON_ASSERT_SIZEOF(MainMode, 0x8C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/Camera/Cameraman/MsgChangeMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::Message::Camera::Cameraman\n{\n    struct MsgChangeMode : SoX::Message<0x14007>\n    {\n        be<uint32_t> PadID{};\n        be<uint32_t> TargetActorID{};\n        bool IsDemoCamera{};\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgChangeMode, PadID, 0x04);\n    MARATHON_ASSERT_OFFSETOF(MsgChangeMode, TargetActorID, 0x08);\n    MARATHON_ASSERT_OFFSETOF(MsgChangeMode, IsDemoCamera, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/HUDButtonWindow/MsgChangeButtons.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::Message::HUDButtonWindow\n{\n    struct MsgChangeButtons : SoX::Message<0x1B05B>\n    {\n        be<uint32_t> ButtonType{};\n\n        MsgChangeButtons(uint32_t buttonType = 0) : ButtonType(buttonType) {}\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgChangeButtons, ButtonType, 0x04);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/HUDGoldMedal/MsgChangeState.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::Message::HUDGoldMedal\n{\n    struct MsgChangeState : SoX::Message<0x1B058>\n    {\n        be<uint32_t> State{};\n        be<uint32_t> Field08{};\n        uint8_t Field0C{};\n        be<uint32_t> EpisodeIndex{};\n\n        MsgChangeState() {}\n\n        MsgChangeState(uint32_t state, uint32_t episodeIndex = 0) : State(state), EpisodeIndex(episodeIndex) {}\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgChangeState, State, 0x04);\n    MARATHON_ASSERT_OFFSETOF(MsgChangeState, Field08, 0x08);\n    MARATHON_ASSERT_OFFSETOF(MsgChangeState, Field0C, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(MsgChangeState, EpisodeIndex, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgChangeState.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::Message::HUDMainMenu\n{\n    struct MsgChangeState : SoX::Message<0x1B053>\n    {\n        be<uint32_t> State{};\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgChangeState, State, 0x04);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgSetCursor.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Message/HUDMainMenu/MsgChangeState.h>\n\nnamespace Sonicteam::Message::HUDMainMenu\n{\n    struct MsgSetCursor : MsgChangeState\n    {\n        be<uint32_t> CursorIndex{};\n\n        MsgSetCursor() {}\n\n        MsgSetCursor(uint32_t state)\n        {\n            State = state;\n        }\n\n        MsgSetCursor(uint32_t state, uint32_t cursorIndex)\n        {\n            State = state;\n            CursorIndex = cursorIndex;\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgSetCursor, CursorIndex, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/HUDMainMenu/MsgTransition.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Message/HUDMainMenu/MsgChangeState.h>\n\nnamespace Sonicteam::Message::HUDMainMenu\n{\n    struct MsgTransition : MsgChangeState\n    {\n        be<uint32_t> Flags{};\n\n        MsgTransition() {}\n\n        MsgTransition(uint32_t state)\n        {\n            State = state;\n        }\n\n        MsgTransition(uint32_t state, uint32_t flags)\n        {\n            State = state;\n            Flags = flags;\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgTransition, Flags, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/Mission/MsgGetGlobalFlag.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Message::Mission\n{\n    struct MsgGetGlobalFlag : SoX::Message<0x1E004>\n    {\n        be<uint32_t> FlagID{};\n        be<uint32_t> FlagValue{};\n\n        MsgGetGlobalFlag(uint32_t flagId) : FlagID(flagId) {}\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgGetGlobalFlag, FlagID, 0x04);\n    MARATHON_ASSERT_OFFSETOF(MsgGetGlobalFlag, FlagValue, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/ObjJump123/MsgGetNextPoint.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::Message::ObjJump123\n{\n    struct MsgGetNextPoint : SoX::Message<0x10007>\n    {\n        SoX::Math::Quaternion Rotation{};\n        SoX::Math::Vector Position{};\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgGetNextPoint, Rotation, 0x10);\n    MARATHON_ASSERT_OFFSETOF(MsgGetNextPoint, Position, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/PauseAdapter/MsgGetText.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Message.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam::Message::PauseAdapter\n{\n    struct MsgGetText : SoX::Message<0x1C003>\n    {\n        stdx::string PauseName{};\n        stdx::string SelectedName{};\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgGetText, PauseName, 0x04);\n    MARATHON_ASSERT_OFFSETOF(MsgGetText, SelectedName, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Message/Player/MsgSuckPlayer.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::Message::Player\n{\n    struct MsgSuckPlayer : SoX::Message<0x11034A>\n    {\n        SoX::Math::Vector Point{};\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MsgSuckPlayer, Point, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MessageWindowTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class MessageWindowTask : public SoX::Engine::Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x44);\n        boost::shared_ptr<TextEntity> m_Field90;\n        boost::shared_ptr<TextEntity> m_Field98;\n        MARATHON_INSERT_PADDING(0x64);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MessageWindowTask, m_Field90, 0x90);\n    MARATHON_ASSERT_OFFSETOF(MessageWindowTask, m_Field98, 0x98);\n    MARATHON_ASSERT_SIZEOF(MessageWindowTask, 0x104);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Mission/Core.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Mission\n{\n    class Core : public SoX::MessageReceiver\n    {\n    public:\n        MARATHON_INSERT_PADDING(0xC81C);\n    };\n\n    MARATHON_ASSERT_SIZEOF(Core, 0xC820);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MovieObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MovieObjectWmv.h>\n\nnamespace Sonicteam\n{\n    class MovieObject : public SoX::RefCountObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        xpointer<MovieObjectWmv> m_pMovieObjectWmv;\n        stdx::string m_Language;\n        xpointer<MyGraphicsDevice> m_pMyGraphicsDevice;\n        MARATHON_INSERT_PADDING(8);\n        be<uint32_t> m_Width;\n        be<uint32_t> m_Height;\n        bool m_Field40;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MovieObject, m_pMovieObjectWmv, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(MovieObject, m_Language, 0x10);\n    MARATHON_ASSERT_OFFSETOF(MovieObject, m_pMyGraphicsDevice, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(MovieObject, m_Width, 0x38);\n    MARATHON_ASSERT_OFFSETOF(MovieObject, m_Height, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(MovieObject, m_Field40, 0x40);\n    MARATHON_ASSERT_SIZEOF(MovieObject, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MovieObjectWmv.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyGraphicsDevice.h>\n#include <Sonicteam/MyTexture.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class MovieObjectWmv : public SoX::RefCountObject\n    {\n    public:\n        bool m_UseCustomDimensions;\n        be<float> m_Left;\n        be<float> m_Top;\n        be<float> m_Right;\n        be<float> m_Bottom;\n        MARATHON_INSERT_PADDING(4);\n        stdx::string m_FilePath;\n        MARATHON_INSERT_PADDING(8);\n        bool m_RenderMovie;\n        MARATHON_INSERT_PADDING(0x38);\n        xpointer<MyGraphicsDevice> m_pMyGraphicsDevice;\n        be<uint32_t> m_Field84;\n        MARATHON_INSERT_PADDING(0x0C);\n        xpointer<MyTexture> m_apTexturesYUV[4 * 3];\n        MARATHON_INSERT_PADDING(8);\n        be<uint32_t> m_Width;\n        be<uint32_t> m_Height;\n        MARATHON_INSERT_PADDING(0x0C);\n        xpointer<void> m_pTechnique;\n        stdx::string m_Language;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_UseCustomDimensions, 0x08);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Left, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Top, 0x10);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Right, 0x14);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Bottom, 0x18);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_FilePath, 0x20);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_RenderMovie, 0x44);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_pMyGraphicsDevice, 0x80);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Field84, 0x84);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_apTexturesYUV, 0x94);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Width, 0xCC);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Height, 0xD0);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_pTechnique, 0xE0);\n    MARATHON_ASSERT_OFFSETOF(MovieObjectWmv, m_Language, 0xE4);\n    MARATHON_ASSERT_SIZEOF(MovieObjectWmv, 0x100);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MovieTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class MovieTask : public SoX::Engine::Task, public CsdLink\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x40);\n        xpointer<MovieObject> m_pMovieObject;\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MovieTask, m_pMovieObject, 0xA4);\n    MARATHON_ASSERT_SIZEOF(MovieTask, 0xC0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyCue.h",
    "content": "#pragma once\n\n#include <Sonicteam/SoX/Audio/Cue.h>\n\nclass MyCue : public Sonicteam::SoX::Audio::Cue {};\n\nMARATHON_ASSERT_SIZEOF(MyCue, 0x2C);\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyCueAdx.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyCue.h>\n#include <Sonicteam/MyCueAttenuate.h>\n#include <Sonicteam/sonicXmaPlayer.h>\n\nclass MyCueAdx : public MyCue, public MyCueAttenuate\n{\npublic:\n    MARATHON_INSERT_PADDING(0x0C);\n    be<float> m_Field50;\n    be<float> m_Field54;\n    xpointer<Sonicteam::sonicXmaPlayer> m_pXmaPlayer;\n    bool m_IsPaused;\n    MARATHON_INSERT_PADDING(3);\n};\n\nMARATHON_ASSERT_OFFSETOF(MyCueAdx, m_Field50, 0x50);\nMARATHON_ASSERT_OFFSETOF(MyCueAdx, m_Field54, 0x54);\nMARATHON_ASSERT_OFFSETOF(MyCueAdx, m_pXmaPlayer, 0x58);\nMARATHON_ASSERT_OFFSETOF(MyCueAdx, m_IsPaused, 0x5C);\nMARATHON_ASSERT_SIZEOF(MyCueAdx, 0x60);\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyCueAttenuate.h",
    "content": "#pragma once\n\nclass MyCueAttenuate\n{\npublic:\n    be<float> m_Time;\n    be<float> m_FadeOutRate;\n    be<float> m_FadeInRate;\n    be<float> m_MinVolume;\n    be<float> m_MaxVolume;\n    bool m_IsFadeOut;\n    MARATHON_INSERT_PADDING(3);\n};\n\nMARATHON_ASSERT_OFFSETOF(MyCueAttenuate, m_Time, 0x00);\nMARATHON_ASSERT_OFFSETOF(MyCueAttenuate, m_FadeOutRate, 0x04);\nMARATHON_ASSERT_OFFSETOF(MyCueAttenuate, m_FadeInRate, 0x08);\nMARATHON_ASSERT_OFFSETOF(MyCueAttenuate, m_MinVolume, 0x0C);\nMARATHON_ASSERT_OFFSETOF(MyCueAttenuate, m_MaxVolume, 0x10);\nMARATHON_ASSERT_OFFSETOF(MyCueAttenuate, m_IsFadeOut, 0x14);\nMARATHON_ASSERT_SIZEOF(MyCueAttenuate, 0x18);\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyGraphicsDevice.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Xenon/DeviceXenon.h>\n\nnamespace Sonicteam\n{\n    class MyGraphicsDevice : public SoX::Graphics::Xenon::DeviceXenon\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1C0);\n    };\n\n    MARATHON_ASSERT_SIZEOF(MyGraphicsDevice, 0x350);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyPhantom.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Physics/Havok/PhantomHavok.h>\n\nnamespace Sonicteam\n{\n    class MyPhantom : public SoX::Physics::Havok::PhantomHavok {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyRenderProcess.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyGraphicsDevice.h>\n#include <Sonicteam/SoX/Engine/RenderProcess.h>\n\nnamespace Sonicteam\n{\n    class MyRenderProcess : public SoX::Engine::RenderProcess\n    {\n    public:\n        xpointer<DocMarathonState> m_pDocMarathonState;\n        xpointer<MyGraphicsDevice> m_pMyGraphicsDevice;\n    };\n\n    MARATHON_ASSERT_SIZEOF(MyRenderProcess, 0x38);\n    MARATHON_ASSERT_OFFSETOF(MyRenderProcess, m_pDocMarathonState, 0x30);\n    MARATHON_ASSERT_OFFSETOF(MyRenderProcess, m_pMyGraphicsDevice, 0x34);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyTexture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Xenon/TextureXenon.h>\n\nnamespace Sonicteam\n{\n    class MyTexture : public SoX::Graphics::Xenon::TextureXenon\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(MyTexture, 0x90);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/MyTransforms.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Transforms.h>\n\nnamespace Sonicteam\n{\n    class MyTransforms : public SoX::Graphics::Transforms\n    {\n    public:\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_SIZEOF(MyTransforms, 0xF8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/NamedActor.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Actor.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class NamedActor : public Actor\n    {\n    public:\n        stdx::string m_Name;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(NamedActor, m_Name, 0x58);\n    MARATHON_ASSERT_SIZEOF(NamedActor, 0x74);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/NamedTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Task.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class NamedTask : public SoX::Engine::Task\n    {\n    public:\n        stdx::string m_Name;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(NamedTask, m_Name, 0x4C);\n    MARATHON_ASSERT_SIZEOF(NamedTask, 0x68);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/NoSyncThread.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Thread.h>\n\nnamespace Sonicteam\n{\n    class NoSyncThread : SoX::Thread\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(NoSyncThread, 0x4C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/ObjectVehicle.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class ObjectVehicle : public Actor\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x180);\n    };\n\n    MARATHON_ASSERT_SIZEOF(ObjectVehicle, 0x1D8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/ObjectVehicleBike.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/VehicleMissileCtrlAutomatic.h>\n\nnamespace Sonicteam\n{\n    class ObjectVehicleBike : public ObjectVehicle\n    {\n    public:\n        MARATHON_INSERT_PADDING(0xD8);\n        xpointer<VehicleMissileCtrlAutomatic> m_pVehicleMissileCtrlAutomatic;\n        MARATHON_INSERT_PADDING(0xAC);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ObjectVehicleBike, m_pVehicleMissileCtrlAutomatic, 0x2B0);\n    MARATHON_ASSERT_SIZEOF(ObjectVehicleBike, 0x360);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/PauseAdapter.h",
    "content": "#pragma once\n\n#include <Sonicteam/GameImp.h>\n\nnamespace Sonicteam\n{\n    class PauseAdapter : public SoX::Engine::Task\n    {\n    public:\n        xpointer<Game> m_pGame;\n        be<uint32_t> m_SelectedID;\n        be<uint32_t> m_Field54;\n        MARATHON_INSERT_PADDING(0x20);\n\n        GameImp* GetGame() const\n        {\n            return (GameImp*)m_pGame.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(PauseAdapter, m_pGame, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(PauseAdapter, m_SelectedID, 0x50);\n    MARATHON_ASSERT_OFFSETOF(PauseAdapter, m_Field54, 0x54);\n    MARATHON_ASSERT_SIZEOF(PauseAdapter, 0x78);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/PauseTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class PauseTask : public SoX::Engine::Task\n    {\n    public:\n        enum PauseTaskState\n        {\n            PauseTaskState_Opened,\n            PauseTaskState_Opening,\n            PauseTaskState_Idle,\n            PauseTaskState_ClosingToAction,\n            PauseTaskState_Closing,\n            PauseTaskState_Closed\n        };\n\n        MARATHON_INSERT_PADDING(4);\n        xpointer<void> m_pHUDPause;\n        MARATHON_INSERT_PADDING(0x18);\n        be<PauseTaskState> m_State;\n        be<uint32_t> m_Flags;\n        be<uint32_t> m_SelectedIndex;\n        MARATHON_INSERT_PADDING(0x28);\n        be<uint32_t> m_Buttons;\n        MARATHON_INSERT_PADDING(0x1E0);\n        be<uint32_t> m_ItemCount;\n        MARATHON_INSERT_PADDING(0x12C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(PauseTask, m_pHUDPause, 0x50);\n    MARATHON_ASSERT_OFFSETOF(PauseTask, m_State, 0x6C);\n    MARATHON_ASSERT_OFFSETOF(PauseTask, m_Flags, 0x70);\n    MARATHON_ASSERT_OFFSETOF(PauseTask, m_SelectedIndex, 0x74);\n    MARATHON_ASSERT_OFFSETOF(PauseTask, m_Buttons, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(PauseTask, m_ItemCount, 0x284);\n    MARATHON_ASSERT_SIZEOF(PauseTask, 0x3B4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/GroundRayListener.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/ICollisionListenerTemplate.h>\n#include <Sonicteam/SoX/Physics/IntersectListener.h>\n#include <Sonicteam/SoX/Physics/IntersectEvent.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/Physics/Entity.h>\n#include <Sonicteam/SoX/LinkNode.h>\n\nnamespace Sonicteam::Player\n{\n    class GroundRayListener : public ICollisionListenerTemplate<SoX::Physics::IntersectListener, SoX::Physics::IntersectEvent>\n    {\n    public:\n        SoX::Math::Vector m_ContactPosition;\n        SoX::Math::Vector m_ContactNormal;\n        be<float> m_RayDistance;\n        be<uint32_t> m_RayFlags;\n        SoX::LinkRef<SoX::Physics::Entity> m_ContactEntity;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(GroundRayListener, m_ContactPosition, 0x50);\n    MARATHON_ASSERT_OFFSETOF(GroundRayListener, m_ContactNormal, 0x60);\n    MARATHON_ASSERT_OFFSETOF(GroundRayListener, m_RayDistance, 0x70);\n    MARATHON_ASSERT_OFFSETOF(GroundRayListener, m_RayFlags, 0x74);\n    MARATHON_ASSERT_OFFSETOF(GroundRayListener, m_ContactEntity, 0x78);\n    MARATHON_ASSERT_SIZEOF(GroundRayListener, 0x90);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/ICollisionListener.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class ICollisionListener\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x0C);\n        be<uint32_t> m_FlagsA;\n        be<uint32_t> m_FlagsB;\n        be<uint32_t> m_FlagsC;\n        MARATHON_INSERT_PADDING(4);\n        SoX::Math::Vector m_SurfaceNormal;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ICollisionListener, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(ICollisionListener, m_FlagsA, 0x10);\n    MARATHON_ASSERT_OFFSETOF(ICollisionListener, m_FlagsB, 0x14);\n    MARATHON_ASSERT_OFFSETOF(ICollisionListener, m_FlagsC, 0x18);\n    MARATHON_ASSERT_OFFSETOF(ICollisionListener, m_SurfaceNormal, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/ICollisionListenerTemplate.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/ICollisionListener.h>\n\nnamespace Sonicteam::Player\n{\n    template <typename TListener, typename TEvent>\n    class ICollisionListenerTemplate : public ICollisionListener, public TListener {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IDynamicLink.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IDynamicLink\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IDynamicLink, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IDynamicLink, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IEventerListener.h",
    "content": "#pragma once\n\nnamespace Sonicteam::Player\n{\n    class IEventerListener\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IEventerListener, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IEventerListener, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IExportExternalFlag.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IExportExternalFlag\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IExportExternalFlag, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IExportExternalFlag, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IExportPostureRequestFlag.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IExportPostureRequestFlag\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IExportPostureRequestFlag, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IExportPostureRequestFlag, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IExportWeaponRequestFlag.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IExportWeaponRequestFlag\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IExportWeaponRequestFlag, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IExportWeaponRequestFlag, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IFlagCommunicator.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IFlagCommunicator\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IFlagCommunicator, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IFlagCommunicator, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IGauge.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IPlugIn.h>\n#include <Sonicteam/Player/IVariable.h>\n#include <Sonicteam/Player/IStepable.h>\n\nnamespace Sonicteam::Player\n{\n    class IGauge : public IPlugIn, public IVariable, public IStepable {};\n\n    MARATHON_ASSERT_SIZEOF(IGauge, 0x28);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/INotification.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class INotification\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(INotification, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(INotification, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPlugIn.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <stdx/string.h>\n\nnamespace Sonicteam::Player\n{\n    class IPlugIn\n    {\n    public:\n        xpointer<void> m_pVftable;\n        stdx::string m_Name;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPlugIn, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(IPlugIn, m_Name, 0x04);\n    MARATHON_ASSERT_SIZEOF(IPlugIn, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPostureControl.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IFlagCommunicator.h>\n#include <Sonicteam/Player/State/ICommonContextIF.h>\n#include <Sonicteam/Player/IDynamicLink.h>\n#include <Sonicteam/Player/IVariable.h>\n#include <Sonicteam/Player/IPosturePlugIn.h>\n#include <Sonicteam/Player/Unit/ITestCase.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/Engine/Task.h>\n#include <Sonicteam/SoX/Math/Quaternion.h>\n#include <Sonicteam/SoX/Physics/World.h>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/ActorManager.h>\n\nnamespace Sonicteam::Player\n{\n    class IPostureControl : public IVariable, public IDynamicLink, public Unit::ITestCase, public IFlagCommunicator\n    {\n    public:\n        SoX::RefSharedPointer<> m_spRootFrame;\n        SoX::Math::Quaternion m_RotationFixed;\n        SoX::Math::Vector m_PositionFixed;\n        SoX::RefSharedPointer<SoX::Physics::World> m_spWorld;\n        boost::shared_ptr<void> m_spGravity;\n        boost::shared_ptr<void> m_spInputListener;\n        boost::shared_ptr<void> m_spAmigoListener;\n        boost::shared_ptr<State::ICommonContextIF> m_spCommonContextIF;\n        boost::shared_ptr<ActorManager> m_spActorManager;\n        xpointer<SoX::Engine::Task> m_pTask;\n        boost::shared_ptr<IPosturePlugIn> m_spPosturePlugIn;\n        SoX::Math::Vector m_GravityDirection;\n        be<float> m_GravityForce;\n        SoX::Math::Vector m_SurfaceNormal;\n        SoX::Math::Vector m_Position;\n        SoX::Math::Quaternion m_Rotation;\n        MARATHON_INSERT_PADDING(0x20);\n        be<uint32_t> m_PostureFlag;\n        be<float> m_ImpulseForward;\n        be<float> m_ImpulseVertical;\n        SoX::Math::Vector m_ImpulseUp;\n        be<uint32_t> m_CommonContextIFFlags;\n        be<uint32_t> m_PostureRequestFlags;\n        be<uint32_t> m_PostureFlags118;\n        be<uint32_t> m_PostureFlags11C;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spRootFrame, 0x10);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_RotationFixed, 0x20);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_PositionFixed, 0x30);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spWorld, 0x40);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spGravity, 0x44);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spInputListener, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spAmigoListener, 0x54);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spCommonContextIF, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spActorManager, 0x64);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_pTask, 0x6C);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_spPosturePlugIn, 0x70);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_GravityDirection, 0x80);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_GravityForce, 0x90);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_SurfaceNormal, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_Position, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_Rotation, 0xC0);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_PostureFlag, 0xF0);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_ImpulseForward, 0xF4);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_ImpulseVertical, 0xF8);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_ImpulseUp, 0x100);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_CommonContextIFFlags, 0x110);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_PostureRequestFlags, 0x114);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_PostureFlags118, 0x118);\n    MARATHON_ASSERT_OFFSETOF(IPostureControl, m_PostureFlags11C, 0x11C);\n    MARATHON_ASSERT_SIZEOF(IPostureControl, 0x120);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPosturePlugIn.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IPosturePlugIn\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPosturePlugIn, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IPosturePlugIn, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPostureSupportEdge.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IPostureSupportEdge\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x6C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportEdge, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IPostureSupportEdge, 0x70);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPostureSupportInput.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IPostureSupportInput\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x2C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportInput, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IPostureSupportInput, 0x30);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPostureSupportOttoto.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IPostureSupportOttoto\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x6C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportOttoto, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IPostureSupportOttoto, 0x70);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPostureSupportRayTemplate.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Physics/World.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/Player/RootFrame.h>\n\nnamespace Sonicteam::Player\n{\n    template <typename TCollisionListener>\n    class IPostureSupportRayTemplate\n    {\n    public:\n        xpointer<void> m_pVftable;\n        SoX::RefSharedPointer<SoX::Physics::World> m_spWorld;\n        boost::shared_ptr<TCollisionListener> m_spCollisionListener;\n        xpointer<Player::RootFrame> m_pRootFrame;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportRayTemplate<void>, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportRayTemplate<void>, m_spWorld, 0x04);\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportRayTemplate<void>, m_spCollisionListener, 0x08);\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportRayTemplate<void>, m_pRootFrame, 0x10);\n    MARATHON_ASSERT_SIZEOF(IPostureSupportRayTemplate<void>, 0x14);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IPostureSupportSphere.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IPostureSupportSphere\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0xCC);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IPostureSupportSphere, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IPostureSupportSphere, 0xD0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IScore.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IScore : public IPlugIn {};\n\n    MARATHON_ASSERT_SIZEOF(IScore, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IStepable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IStepable\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IStepable, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IStepable, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IVariable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class IVariable\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IVariable, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IVariable, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/IZock.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IPlugIn.h>\n\nnamespace Sonicteam::Player\n{\n    class IZock : public IPlugIn {};\n\n    MARATHON_ASSERT_SIZEOF(IZock, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Input/IListener.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::Input\n{\n    class IListener\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x14);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IListener, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IListener, 0x18);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Input/ListenerNormal.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/Input/TimedAction.h>\n\nnamespace Sonicteam::Player::Input\n{\n    class ListenerNormal : public IListener, public IPlugIn, public IVariable, public IStepable\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        xpointer<bool> m_pIsListening;\n        be<uint32_t> m_State;\n        MARATHON_INSERT_PADDING(0x50);\n        be<SoX::Input::KeyState> m_ActionA;\n        be<SoX::Input::KeyState> m_ActionB;\n        be<SoX::Input::KeyState> m_ActionC;\n        be<SoX::Input::KeyState> m_ActionD;\n        TimedAction m_TimedAction;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_pIsListening, 0x44);\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_State, 0x48);\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_ActionA, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_ActionB, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_ActionC, 0xA4);\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_ActionD, 0xA8);\n    MARATHON_ASSERT_OFFSETOF(ListenerNormal, m_TimedAction, 0xAC);\n    MARATHON_ASSERT_SIZEOF(ListenerNormal, 0xC0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Input/TimedAction.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::Input\n{\n    struct TimedAction\n    {\n        be<float> m_Time;\n        be<SoX::Input::KeyState> m_Action;\n        bool m_Field08;\n        bool m_IsDoubleTapped;\n        bool m_Field0A;\n        bool m_Field0B;\n        be<float> m_Field0C;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_Time, 0x00);\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_Action, 0x04);\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_Field08, 0x08);\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_IsDoubleTapped, 0x09);\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_Field0A, 0x0A);\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_Field0B, 0x0B);\n    MARATHON_ASSERT_OFFSETOF(TimedAction, m_Field0C, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Object.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <boost/smart_ptr/shared_ptr.h>\n#include <Sonicteam/Camera/Cameraman.h>\n#include <Sonicteam/Player/State/Machine2.h>\n#include <Sonicteam/Player/IGauge.h>\n#include <Sonicteam/Player/IPlugIn.h>\n#include <Sonicteam/Player/RootFrame.h>\n#include <Sonicteam/Player/IPostureControl.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <stdx/vector.h>\n\nnamespace Sonicteam::Player\n{\n    class Object : public Actor\n    {\n    public:\n        struct CreationParams\n        {\n            xpointer<const char> pPlayerLua;\n            MARATHON_INSERT_PADDING(0x2C);\n            SoX::Math::Vector Position;\n        };\n\n        struct EquipFlags\n        {\n            be<uint32_t> GlobalFlag;\n            be<uint32_t> EquipFlag;\n        };\n\n        stdx::string m_LuaFile;\n        stdx::string m_PackageFile;\n        be<uint32_t> m_TargetCameraActorID; \n        xpointer<SoX::MessageReceiver> m_pCameraman;\n        be<uint32_t> m_Index;\n        be<uint32_t> m_PadID;\n        SoX::Math::Quaternion m_SpawnRotation;\n        SoX::Math::Vector m_SpawnPosition;\n        be<uint32_t> m_SpawnRingCount;\n        SoX::RefSharedPointer<> m_spSpawnSource;\n        bool m_IsPlayer;\n        bool m_IsPosture;\n        bool m_IsAmigo;\n        MARATHON_INSERT_PADDING(1);\n        SoX::RefSharedPointer<RootFrame> m_spRootFrame;\n        SoX::RefSharedPointer<> m_spPackageBinary;\n        boost::shared_ptr<void> m_spModel;\n        boost::shared_ptr<IPostureControl> m_spPostureControl;\n        boost::shared_ptr<State::Machine2> m_spStateMachine;\n        boost::shared_ptr<void> m_spGravity;\n        boost::shared_ptr<void> m_spImpulse;\n        be<uint32_t> m_SetupModuleIndexPrefix;\n        be<uint32_t> m_SetupModuleIndexPostfix;\n        boost::shared_ptr<IGauge> m_spGauge;\n        MARATHON_INSERT_PADDING(8);\n        stdx::vector<boost::shared_ptr<IPlugIn>> m_vspPlugins;\n        MARATHON_INSERT_PADDING(0x58);\n        be<float> m_DeltaTime;\n        MARATHON_INSERT_PADDING(0x48);\n        stdx::vector<EquipFlags> m_vEquipFlags;\n        stdx::string m_Name;\n        MARATHON_INSERT_PADDING(0x110);\n\n        template <typename T = IGauge>\n        T* GetGauge()\n        {\n            return (T*)m_spGauge.get();\n        }\n\n        template <typename T = IPlugIn>\n        inline T* GetPlugin(const char* pluginName)\n        {\n            for (auto& spPlugin : m_vspPlugins)\n            {\n                if (spPlugin->m_Name == pluginName)\n                    return static_cast<T*>(spPlugin.get());\n            }\n\n            return nullptr;\n        }\n\n        SoX::Input::Manager* GetInputManager()\n        {\n            if (!m_IsPlayer)\n                return nullptr;\n\n            auto pDoc = GetDoc<DocMarathonState>();\n            auto pGame = pDoc->GetDocMode<GameMode>()->GetGame();\n            auto playerIndex = pGame->PlayerActorIDToIndex(m_ActorID);\n            auto padID = pDoc->m_aPadIDs[playerIndex];\n\n            return pDoc->m_vspInputManager[padID].get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Object::CreationParams, pPlayerLua, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Object::CreationParams, Position, 0x30);\n\n    MARATHON_ASSERT_OFFSETOF(Object::EquipFlags, GlobalFlag, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Object::EquipFlags, EquipFlag, 0x04);\n\n    MARATHON_ASSERT_OFFSETOF(Object, m_LuaFile, 0x58);\n    MARATHON_ASSERT_OFFSETOF(Object, m_PackageFile, 0x74);\n    MARATHON_ASSERT_OFFSETOF(Object, m_TargetCameraActorID, 0x90);\n    MARATHON_ASSERT_OFFSETOF(Object, m_pCameraman, 0x94);\n    MARATHON_ASSERT_OFFSETOF(Object, m_Index, 0x98);\n    MARATHON_ASSERT_OFFSETOF(Object, m_PadID, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(Object, m_SpawnRotation, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(Object, m_SpawnPosition, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(Object, m_SpawnRingCount, 0xC0);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spSpawnSource, 0xC4);\n    MARATHON_ASSERT_OFFSETOF(Object, m_IsPlayer, 0xC8);\n    MARATHON_ASSERT_OFFSETOF(Object, m_IsPosture, 0xC9);\n    MARATHON_ASSERT_OFFSETOF(Object, m_IsAmigo, 0xCA);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spRootFrame, 0xCC);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spPackageBinary, 0xD0);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spModel, 0xD4);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spPostureControl, 0xDC);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spStateMachine, 0xE4);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spGravity, 0xEC);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spImpulse, 0xF4);\n    MARATHON_ASSERT_OFFSETOF(Object, m_SetupModuleIndexPrefix, 0xFC);\n    MARATHON_ASSERT_OFFSETOF(Object, m_SetupModuleIndexPostfix, 0x100);\n    MARATHON_ASSERT_OFFSETOF(Object, m_spGauge, 0x104);\n    MARATHON_ASSERT_OFFSETOF(Object, m_vspPlugins, 0x114);\n    MARATHON_ASSERT_OFFSETOF(Object, m_DeltaTime, 0x17C);\n    MARATHON_ASSERT_OFFSETOF(Object, m_vEquipFlags, 0x1C8);\n    MARATHON_ASSERT_OFFSETOF(Object, m_Name, 0x1D8);\n    MARATHON_ASSERT_SIZEOF(Object, 0x310);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/PostureControl.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IPostureControl.h>\n#include <Sonicteam/Player/IPostureSupportSphere.h>\n#include <Sonicteam/Player/IPostureSupportOttoto.h>\n#include <Sonicteam/Player/IPostureSupportEdge.h>\n#include <Sonicteam/Player/IPostureSupportInput.h>\n#include <Sonicteam/Player/IPostureSupportRayTemplate.h>\n#include <Sonicteam/Player/GroundRayListener.h>\n\nnamespace Sonicteam::Player\n{\n    class PostureControl : public IPostureControl, public IPostureSupportSphere, public IPostureSupportOttoto, public IPostureSupportEdge, public IPostureSupportInput, public IPostureSupportRayTemplate<GroundRayListener>\n    {\n    public:\n        enum PostureFlag\n        {\n            // The player is grounded.\n            PostureFlag_Grounded = 0x01,\n\n            // The player is brushing against a wall.\n            PostureFlag_WallSide = 0x08,\n\n            // The player is head on against a wall.\n            PostureFlag_WallFront = 0x10,\n\n            // The player is grinding on a rail.\n            PostureFlag_RailGrind = 0x40,\n\n            // The player is in the intermediate state between jumping and falling.\n            PostureFlag_FallIntermediate = 0x100,\n\n            // The player is falling.\n            PostureFlag_Fall = 0x200,\n\n            // The player is on water collision.\n            PostureFlag_Water = 0x800,\n\n            // The player is light dashing.\n            PostureFlag_LightDash = 0x4000,\n\n            // The player is rotating in a non-forward direction.\n            PostureFlag_QuickRotate = 0x8000,\n\n            // The player is on tentative collision.\n            PostureFlag_Tentative = 0x10000,\n\n            // The player is water sliding.\n            PostureFlag_WaterSlide = 0x20000,\n\n            // The player is on grass collision.\n            PostureFlag_Grass = 0x100000,\n\n            // The player is on dirt collision.\n            PostureFlag_Dirt = 0x200000,\n\n            // The player is on stone collision.\n            PostureFlag_Stone = 0x400000,\n\n            // The player is on sand collision.\n            PostureFlag_Sand = 0x1000000\n        };\n\n        MARATHON_INSERT_PADDING(0xE0);\n    };\n\n    MARATHON_ASSERT_SIZEOF(PostureControl, 0x400);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/RootFrame.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IExportExternalFlag.h>\n#include <Sonicteam/Player/IPlugIn.h>\n#include <Sonicteam/SoX/Graphics/Frame.h>\n#include <Sonicteam/SoX/Math/Matrix.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n\nnamespace Sonicteam::Player\n{\n    class RootFrame : public SoX::Graphics::Frame, public IPlugIn, public IExportExternalFlag\n    {\n    public:\n        SoX::Math::Matrix4x4 m_Field70;\n        SoX::Math::Matrix4x4 m_FieldB0;\n        SoX::Math::Vector m_PositionF0;\n        SoX::Math::Vector m_Field100;\n        SoX::Math::Vector m_Impulse;\n        MARATHON_INSERT_PADDING(0x30);\n        be<uint64_t> m_ExternalFlag;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(RootFrame, m_Field70, 0x70);\n    MARATHON_ASSERT_OFFSETOF(RootFrame, m_FieldB0, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(RootFrame, m_PositionF0, 0xF0);\n    MARATHON_ASSERT_OFFSETOF(RootFrame, m_Field100, 0x100);\n    MARATHON_ASSERT_OFFSETOF(RootFrame, m_Impulse, 0x110);\n    MARATHON_ASSERT_OFFSETOF(RootFrame, m_ExternalFlag, 0x150);\n    MARATHON_ASSERT_SIZEOF(RootFrame, 0x160);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Score.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class Score : public IScore, public IVariable, public IStepable\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x0C);\n        xpointer<Object> m_pPlayer;\n        MARATHON_INSERT_PADDING(0x38);\n    };\n    \n    MARATHON_ASSERT_OFFSETOF(Score, m_pPlayer, 0x34);\n    MARATHON_ASSERT_SIZEOF(Score, 0x70);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/SonicGauge.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player\n{\n    class SonicGauge : public IGauge\n    {\n    public:\n        be<float> m_Value;\n        be<float> m_GroundedTime;\n        be<uint32_t> m_Flags;\n        be<uint32_t> m_GroundedFlags;\n        be<float> c_gauge_max;\n        be<float> c_gauge_green;\n        be<float> c_gauge_red;\n        be<float> c_gauge_blue;\n        be<float> c_gauge_white;\n        be<float> c_gauge_sky;\n        be<float> c_gauge_yellow;\n        be<float> c_gauge_purple;\n        be<float> c_gauge_super;\n        be<float> c_gauge_heal;\n        be<float> c_gauge_heal_delay;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, m_Value, 0x28);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, m_GroundedTime, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, m_Flags, 0x30);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, m_GroundedFlags, 0x34);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_max, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_green, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_red, 0x40);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_blue, 0x44);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_white, 0x48);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_sky, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_yellow, 0x50);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_purple, 0x54);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_super, 0x58);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_heal, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(SonicGauge, c_gauge_heal_delay, 0x60);\n    MARATHON_ASSERT_SIZEOF(SonicGauge, 0x64);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/CommonContext.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/State/ICommonContext.h>\n#include <Sonicteam/Player/IExportPostureRequestFlag.h>\n#include <Sonicteam/Player/IExportWeaponRequestFlag.h>\n#include <Sonicteam/Player/Score.h>\n#include <api/boost/smart_ptr/shared_ptr.h>\n\nnamespace Sonicteam::Player::State\n{\n    class CommonContext : public ICommonContext, public IExportPostureRequestFlag, public IExportWeaponRequestFlag\n    {\n    public:\n        xpointer<void> m_spComboAttackManager;\n        be<uint32_t> m_CommonContextIFFlags1;\n        be<uint32_t> m_CommonContextIFFlags2;\n        be<uint32_t> m_CommonContextIFFlags3;\n        be<uint32_t> m_ExportPostureRequestFlags;\n        be<uint32_t> m_ExportWeaponRequestFlags;\n        MARATHON_INSERT_PADDING(0x14);\n        be<uint32_t> m_PostureFlags;\n        be<uint64_t> m_ExternalFlags;\n        be<uint32_t> m_VehicleFlags;\n        be<uint32_t> m_AmigoFlags;\n        MARATHON_INSERT_PADDING(0x50);\n        boost::shared_ptr<Score> m_spScore;\n        MARATHON_INSERT_PADDING(8);\n        boost::shared_ptr<Input::ListenerNormal> m_spInputListenerNormal;\n        boost::anonymous_shared_ptr m_spInputListenerAmigo;\n        MARATHON_INSERT_PADDING(0xE8);\n\n        SoX::Input::Manager* GetInputManager() const\n        {\n            if (!m_spScore.get())\n                return nullptr;\n\n            return m_spScore->m_pPlayer->GetInputManager();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_spComboAttackManager, 0x98);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_CommonContextIFFlags1, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_CommonContextIFFlags2, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_CommonContextIFFlags3, 0xA4);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_ExportPostureRequestFlags, 0xA8);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_ExportWeaponRequestFlags, 0xAC);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_PostureFlags, 0xC4);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_ExternalFlags, 0xC8);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_VehicleFlags, 0xD0);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_AmigoFlags, 0xD4);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_spScore, 0x128);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_spInputListenerNormal, 0x138);\n    MARATHON_ASSERT_OFFSETOF(CommonContext, m_spInputListenerAmigo, 0x140);\n    MARATHON_ASSERT_SIZEOF(CommonContext, 0x230);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/CommonFall.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/State/CommonObject.h>\n\nnamespace Sonicteam::Player::State\n{\n    class CommonFall : public CommonObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_SIZEOF(CommonFall, 0x14);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/CommonObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/State/Object2.h>\n\nnamespace Sonicteam::Player::State\n{\n    class CommonObject : public Object2 {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/ContextSpeedAndJump.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class ContextSpeedAndJump \n    {\n    public:\n        be<float> m_BaseSpeedForward;\n        be<float> m_GimmickSpeedForward;\n        be<float> m_BaseSpeedVertical;\n        be<float> m_GimmickSpeedVertical;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ContextSpeedAndJump, m_BaseSpeedForward, 0x00);\n    MARATHON_ASSERT_OFFSETOF(ContextSpeedAndJump, m_GimmickSpeedForward, 0x04);\n    MARATHON_ASSERT_OFFSETOF(ContextSpeedAndJump, m_BaseSpeedVertical, 0x08);\n    MARATHON_ASSERT_OFFSETOF(ContextSpeedAndJump, m_GimmickSpeedVertical, 0x0C);\n    MARATHON_ASSERT_SIZEOF(ContextSpeedAndJump, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/ICommonContext.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/State/ContextSpeedAndJump.h>\n#include <Sonicteam/Player/State/ICommonContextIF.h>\n#include <Sonicteam/Player/State/IContext.h>\n\nnamespace Sonicteam::Player::State\n{\n    class ICommonContext : public IContext, public ICommonContextIF, public ContextSpeedAndJump\n    {\n    public:\n        be<uint32_t> m_AnimationID;\n        be<float> m_LockButtons;\n        be<uint32_t> m_LastVelocityForward;\n        be<uint32_t> m_LastVelocityVertical;\n        be<uint32_t> m_LastLockButtons;\n        be<uint32_t> m_Buttons;\n\t\tbe<float> m_CurrentStickBorder;\n        MARATHON_INSERT_PADDING(4);\n\t\tbe<uint32_t> m_AnimationState;\n\t\tMARATHON_INSERT_PADDING(0x2C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_AnimationID, 0x40);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_LockButtons, 0x44);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_LastVelocityForward, 0x48);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_LastVelocityVertical, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_LastLockButtons, 0x50);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_Buttons, 0x54);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_CurrentStickBorder, 0x58);\n    MARATHON_ASSERT_OFFSETOF(ICommonContext, m_AnimationState, 0x60);\n    MARATHON_ASSERT_SIZEOF(ICommonContext, 0x90);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/ICommonContextIF.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class ICommonContextIF\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ICommonContextIF, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(ICommonContextIF, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/IContext.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IPlugIn.h>\n#include <Sonicteam/Player/IVariable.h>\n#include <Sonicteam/Player/IDynamicLink.h>\n#include <Sonicteam/Player/IFlagCommunicator.h>\n\nnamespace Sonicteam::Player::State\n{\n    class IContext : public IPlugIn, public IVariable, public IDynamicLink, public IFlagCommunicator {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/IMachine.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class IMachine : public IPlugIn {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/Machine2.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/State/IContext.h>\n#include <Sonicteam/Player/State/IMachine.h>\n#include <Sonicteam/SoX/AI/StateMachine.h>\n\nnamespace Sonicteam::Player::State\n{\n    class Machine2 : public SoX::AI::StateMachine<IContext>, public IMachine\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4C);\n\n        inline SoX::AI::StateMachine<IContext>* GetBase()\n        {\n            return (SoX::AI::StateMachine<IContext>*)((uint8_t*)this - 0x20);\n        }\n    };\n\n    MARATHON_ASSERT_SIZEOF(Machine2, 0x78);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/Object2.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class Object2 : public SoX::AI::StateMachine<IContext> {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/SonicContext.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/State/CommonContext.h>\n#include <Sonicteam/Player/SonicGauge.h>\n#include <boost/smart_ptr/shared_ptr.h>\n\nnamespace Sonicteam::Player::State\n{\n    class SonicContext : public CommonContext\n    {\n    public:\n        enum GemSprite : uint32_t\n        {\n            GemSprite_Green = 1,\n            GemSprite_Red,\n            GemSprite_Blue,\n            GemSprite_White,\n            GemSprite_Sky,\n            GemSprite_Yellow,\n            GemSprite_Purple,\n            GemSprite_Super\n        };\n\n        enum Gem : uint32_t\n        {\n            Gem_Blue = 1,\n            Gem_Red,\n            Gem_Green,\n            Gem_Purple,\n            Gem_Sky,\n            Gem_White,\n            Gem_Yellow,\n            Gem_Super\n        };\n\n        static constexpr GemSprite ms_GemSpriteConversionTable[8] =\n        {\n            GemSprite_Blue,\n            GemSprite_Red,\n            GemSprite_Green,\n            GemSprite_Purple,\n            GemSprite_Sky,\n            GemSprite_White,\n            GemSprite_Yellow,\n            GemSprite_Super\n        };\n\n        be<GemSprite> m_CurrentGemSprite;\n        boost::shared_ptr<SonicGauge> m_Gauge;\n        uint8_t m_HomingLockOn;\n        uint8_t m_DisablePlayerMovement;\n        uint8_t m_AntigravityHitBox;\n        uint8_t m_Field23F;\n        uint8_t m_BoundAttackHitBox;\n        uint8_t m_Field241;\n        uint8_t m_Shrink;\n        uint8_t m_ThunderGuard;\n        uint8_t m_Tornado;\n        uint8_t m_FPS;\n        uint8_t m_ThrowGem;\n        uint8_t m_SlowTime;\n        uint8_t m_MachAura;\n        uint8_t m_GemsEnabled;\n        uint8_t m_Field24A;\n        uint8_t m_Field24B;\n        be<uint32_t> m_HomingFlip;\n        be<Gem> m_CurrentGem;\n        MARATHON_INSERT_PADDING(0x58);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_CurrentGemSprite, 0x230);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Gauge, 0x234);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_HomingLockOn, 0x23C);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_DisablePlayerMovement, 0x23D);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_AntigravityHitBox, 0x23E);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Field23F, 0x23F);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_BoundAttackHitBox, 0x240);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Field241, 0x241);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Shrink, 0x242);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_ThunderGuard, 0x243);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Tornado, 0x244);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_FPS, 0x245);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_ThrowGem, 0x246);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_SlowTime, 0x247);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_MachAura, 0x248);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_GemsEnabled, 0x249);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Field24A, 0x24A);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_Field24B, 0x24B);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_HomingFlip, 0x24C);\n    MARATHON_ASSERT_OFFSETOF(SonicContext, m_CurrentGem, 0x250);\n    MARATHON_ASSERT_SIZEOF(SonicContext, 0x2B0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/SonicObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class SonicObject : public CommonObject {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/TailsContext.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class TailsContext : public CommonContext\n    {\n    public:\n        be<float> m_FlightTime;\n        MARATHON_INSERT_PADDING(8);\n        be<float> m_FlightDuration;\n        be<float> m_FlightLimit;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TailsContext, m_FlightTime, 0x230);\n    MARATHON_ASSERT_OFFSETOF(TailsContext, m_FlightDuration, 0x23C);\n    MARATHON_ASSERT_OFFSETOF(TailsContext, m_FlightLimit, 0x240);\n    MARATHON_ASSERT_SIZEOF(TailsContext, 0x250);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/State/TailsFlight.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::State\n{\n    class TailsFlight : public CommonFall\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        be<float> m_FlightTime;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TailsFlight, m_FlightTime, 0x18);\n    MARATHON_ASSERT_SIZEOF(TailsFlight, 0x1C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Unit/ITestCase.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::Player::Unit\n{\n    class ITestCase\n\t{\n    public:\n        xpointer<void> m_pVftable;\n\t};\n\n    MARATHON_ASSERT_OFFSETOF(ITestCase, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(ITestCase, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Weapon/SonicWeapons.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IPlugIn.h>\n#include <Sonicteam/Player/IFlagCommunicator.h>\n#include <Sonicteam/Player/IStepable.h>\n#include <Sonicteam/Player/IDynamicLink.h>\n#include <Sonicteam/Player/IVariable.h>\n#include <Sonicteam/Player/INotification.h>\n#include <Sonicteam/SoX/Engine/Task.h>\n#include <Sonicteam/SoX/LinkNode.h>\n\nnamespace Sonicteam::Player::Weapon\n{  \n    class SonicWeapons : public IPlugIn, public IFlagCommunicator, public IStepable, public IDynamicLink, public IVariable, public INotification\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4C);\n        SoX::LinkRef<SoX::Engine::Task> m_GunDrive;\n        MARATHON_INSERT_PADDING(0x28);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SonicWeapons, m_GunDrive, 0x80);\n    MARATHON_ASSERT_SIZEOF(SonicWeapons, 0xB8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/Player/Zock.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Player/IZock.h>\n#include <Sonicteam/Player/IFlagCommunicator.h>\n#include <Sonicteam/Player/IStepable.h>\n#include <Sonicteam/Player/IDynamicLink.h>\n#include <Sonicteam/Player/IVariable.h>\n#include <Sonicteam/MyPhantom.h>\n#include <Sonicteam/SoX/Physics/World.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/Player/RootFrame.h>\n#include <Sonicteam/SoX/Physics/PhantomListener.h>\n\nnamespace Sonicteam::Player\n{\n    class Zock : public IZock, public IFlagCommunicator, public IStepable, public IDynamicLink, public IVariable\n    {\n    public:\n        SoX::RefSharedPointer<SoX::Physics::World> m_spWorld;\n        SoX::RefSharedPointer<SoX::Physics::World> m_spRootFrame;\n        SoX::RefSharedPointer<MyPhantom> m_spPhantomA;\n        MARATHON_INSERT_PADDING(0x20);\n        SoX::RefSharedPointer<MyPhantom> m_spPhantomB;\n        MARATHON_INSERT_PADDING(0x40);\n        SoX::RefSharedPointer<SoX::Physics::PhantomListener> m_spPhantomListener;\n        MARATHON_INSERT_PADDING(0x3C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Zock, m_spWorld, 0x30);\n    MARATHON_ASSERT_OFFSETOF(Zock, m_spRootFrame, 0x34);\n    MARATHON_ASSERT_OFFSETOF(Zock, m_spPhantomA, 0x38);\n    MARATHON_ASSERT_OFFSETOF(Zock, m_spPhantomB, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(Zock, m_spPhantomListener, 0xA0);\n    MARATHON_ASSERT_SIZEOF(Zock, 0xE0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/PropFixture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/Fixture.h>\n\nnamespace Sonicteam\n{\n    class PropFixture : public Fixture\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x10);\n    };\n\n    MARATHON_ASSERT_SIZEOF(PropFixture, 0x180);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RaderMapManager.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class RaderMapManager : public Actor\n    {\n    public:\n        xpointer<void> m_pClump;\n        xpointer<SoX::Graphics::FrameGP> m_pFrameGP1;\n        xpointer<SoX::Graphics::FrameGP> m_pFrameGP2;\n        MARATHON_INSERT_PADDING(0x0C);\n        SoX::Math::Vector m_Transform;\n        be<float> m_Zoom;\n        MARATHON_INSERT_PADDING(0x0C);\n        SoX::Math::Vector m_Field90;\n        MARATHON_INSERT_PADDING(0x10);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(RaderMapManager, m_pClump, 0x58);\n    MARATHON_ASSERT_OFFSETOF(RaderMapManager, m_pFrameGP1, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(RaderMapManager, m_pFrameGP2, 0x60);\n    MARATHON_ASSERT_OFFSETOF(RaderMapManager, m_Transform, 0x70);\n    MARATHON_ASSERT_OFFSETOF(RaderMapManager, m_Zoom, 0x80);\n    MARATHON_ASSERT_OFFSETOF(RaderMapManager, m_Field90, 0x90);\n    MARATHON_ASSERT_SIZEOF(RaderMapManager, 0xB0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ApplyBloom.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ApplyBloom : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(ApplyBloom, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ApplyDevice.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ApplyDevice : public MyRenderProcess {};\n\n    MARATHON_ASSERT_SIZEOF(ApplyDevice, 0x38);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ApplySceneParams.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ApplySceneParams : public MyRenderProcess {};\n\n    MARATHON_ASSERT_SIZEOF(ApplySceneParams, 0x38);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/AutoSetAspect.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ApplyAutoAspect : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_CameraIndex;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(ApplyAutoAspect, m_CameraIndex, 0x38);\n    MARATHON_ASSERT_SIZEOF(ApplyAutoAspect, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/Capture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class Capture : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4);\n        xpointer<void> m_pFramebufferHDR;\n        MARATHON_INSERT_PADDING(0x14);\n        xpointer<void> m_pFBO1;\n        MARATHON_INSERT_PADDING(0x10);\n        xpointer<void> m_pFBO2;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Capture, m_pFramebufferHDR, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(Capture, m_pFBO1, 0x54);\n    MARATHON_ASSERT_OFFSETOF(Capture, m_pFBO2, 0x68);\n    MARATHON_ASSERT_SIZEOF(Capture, 0x6C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ClearRenderTarget.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ClearRenderTarget : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x8);\n    };\n\n    MARATHON_ASSERT_SIZEOF(ClearRenderTarget, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ColorFill.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ColorFill : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x10);\n    };\n\n    MARATHON_ASSERT_SIZEOF(ColorFill, 0x48);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/CopyTexture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class CopyTexture : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4);\n        xpointer<SoX::Graphics::Texture> m_pTexture;\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CopyTexture, m_pTexture, 0x3C);\n    MARATHON_ASSERT_SIZEOF(CopyTexture, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/LockBlendMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class LockBlendMode : public MyRenderProcess\n    {\n    public:\n        bool m_Lock;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(LockBlendMode, m_Lock, 0x38);\n    MARATHON_ASSERT_SIZEOF(LockBlendMode, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/LockCullMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class LockCullMode : public MyRenderProcess\n    {\n    public:\n        bool m_Lock;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(LockCullMode, m_Lock, 0x38);\n    MARATHON_ASSERT_SIZEOF(LockCullMode, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/LockZMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class LockZMode : public MyRenderProcess\n    {\n    public:\n        bool m_Lock;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(LockZMode, m_Lock, 0x38);\n    MARATHON_ASSERT_SIZEOF(LockZMode, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/MakeBloom.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class MakeBloom : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x34);\n    };\n\n    MARATHON_ASSERT_SIZEOF(MakeBloom, 0x6C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/Movie.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class Movie : public MyRenderProcess\n    {\n    public:\n        xpointer<MovieObject> m_pMovieObject;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Movie, m_pMovieObject, 0x38);\n    MARATHON_ASSERT_SIZEOF(Movie, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/PrepareCalculateCSM.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class PrepareCalculateCSM : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_SIZEOF(PrepareCalculateCSM, 0x50);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/Rasterize.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class Rasterize : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4);\n        xpointer<SoX::Graphics::Texture> m_pTexture;\n        bool m_Flag;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Rasterize, m_pTexture, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(Rasterize, m_Flag, 0x40);\n    MARATHON_ASSERT_SIZEOF(Rasterize, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/RasterizeBurnoutBlur.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class RasterizeBurnoutBlur : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x10);\n    };\n\n    MARATHON_ASSERT_SIZEOF(RasterizeBurnoutBlur, 0x48);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ResetRenderStates.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ResetRenderStates : public MyRenderProcess {};\n\n    MARATHON_ASSERT_SIZEOF(ResetRenderStates, 0x38);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ResetScissorRect.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ResetScissorRect : public MyRenderProcess {};\n\n    MARATHON_ASSERT_SIZEOF(ResetScissorRect, 0x38);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/ResetViewport.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class ResetViewport : public MyRenderProcess {};\n\n    MARATHON_ASSERT_SIZEOF(ResetViewport, 0x38);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/Resolve.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class Resolve : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x8);\n        xpointer<void> m_pFBO;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Resolve, m_pFBO, 0x40);\n    MARATHON_ASSERT_SIZEOF(Resolve, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/RunCommandBuffer.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class RunCommandBuffer : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_CameraIndex;\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(RunCommandBuffer, m_CameraIndex, 0x38);\n    MARATHON_ASSERT_SIZEOF(RunCommandBuffer, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetAutoZPass.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetAutoZPass : public MyRenderProcess\n    {\n    public:\n        bool m_Enabled;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetAutoZPass, m_Enabled, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetAutoZPass, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetBackStencilOp.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <d3dxb.h>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetBackStencilOp : public MyRenderProcess\n    {\n    public:\n        be<D3DXBSTENCILOP> m_Field38;\n        be<D3DXBSTENCILOP> m_Field3C;\n        be<D3DXBSTENCILOP> m_Field40;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetBackStencilOp, m_Field38, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetBackStencilOp, m_Field3C, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetBackStencilOp, m_Field40, 0x40);\n    MARATHON_ASSERT_SIZEOF(SetBackStencilOp, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetBlendMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetBlendMode : public MyRenderProcess\n    {\n    public:\n        enum BlendMode\n        {\n            BlendMode_Opaque = 0,\n            BlendMode_Blend = 1,\n            BlendMode_Add = 2\n        };\n\n        be<BlendMode> m_BlendMode;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetBlendMode, m_BlendMode, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetBlendMode, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetCSMTextures.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetCSMTextures : public MyRenderProcess\n    {\n    public:\n        bool m_Flag;\n        xpointer<SoX::Graphics::Texture> m_pCSMTexture;\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetCSMTextures, m_Flag, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetCSMTextures, m_pCSMTexture, 0x3C);\n    MARATHON_ASSERT_SIZEOF(SetCSMTextures, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetClip.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetClip : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_CameraIndex;\n        be<float> m_Clip1;\n        be<float> m_Clip2;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetClip, m_CameraIndex, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetClip, m_Clip1, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetClip, m_Clip2, 0x40);\n    MARATHON_ASSERT_SIZEOF(SetClip, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetColorWriteEnable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetColorWriteEnable : public MyRenderProcess\n    {\n    public:\n        enum ColorWrite : uint32_t\n        {\n            ColorWrite_None = 0,\n            ColorWrite_Red = 1 << 0,\n            ColorWrite_Green = 1 << 1,\n            ColorWrite_Blue = 1 << 2,\n            ColorWrite_Alpha = 1 << 3\n        };\n\n        be<ColorWrite> m_ColorWriteMask;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetColorWriteEnable, m_ColorWriteMask, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetColorWriteEnable, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetConstantShader.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetConstantShader : public MyRenderProcess\n    {\n    public:\n        enum PassIndex\n        {\n            PassIndex_Main,\n            PassIndex_Transparent,\n            PassIndex_Sky,\n            PassIndex_Shadowmap,\n            PassIndex_Psi,\n            PassIndex_Oc,\n            PassIndex_Glare,\n            PassIndex_AfterPp,\n            PassIndex_Radermap,\n            PassIndex_User0,\n            PassIndex_User1\n        };\n\n        be<PassIndex> m_PassIndex;\n        xpointer<void> m_pShader;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetConstantShader, m_PassIndex, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetConstantShader, m_pShader, 0x3C);\n    MARATHON_ASSERT_SIZEOF(SetConstantShader, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetCullMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetCullMode : public MyRenderProcess\n    {\n    public:\n        enum CullMode\n        {\n            CullMode_None,\n            CullMode_CW,\n            CullMode_CCW\n        };\n\n        be<CullMode> m_CullMode;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetCullMode, m_CullMode, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetCullMode, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetCurrentScreen.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetCurrentScreen : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_ScreenIndex;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetCurrentScreen, m_ScreenIndex, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetCurrentScreen, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetDepthTextures.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetDepthTextures : public MyRenderProcess\n    {\n    public:\n        bool m_Flag;\n        xpointer<SoX::Graphics::Texture> m_pDepthTexture;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetDepthTextures, m_Flag, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetDepthTextures, m_pDepthTexture, 0x3C);\n    MARATHON_ASSERT_SIZEOF(SetDepthTextures, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetFovY.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetFovY : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_CameraIndex;\n        be<float> m_FovY;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetFovY, m_CameraIndex, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetFovY, m_FovY, 0x3C);\n    MARATHON_ASSERT_SIZEOF(SetFovY, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetFrameBufferObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetFrameBufferObject : public MyRenderProcess\n    {\n    public:\n        xpointer<void> m_pFrameBufferObject;\n        MARATHON_INSERT_PADDING(0x4);\n        bool m_Once;\n        bool m_PostProcess;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetFrameBufferObject, m_pFrameBufferObject, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetFrameBufferObject, m_Once, 0x40);\n    MARATHON_ASSERT_OFFSETOF(SetFrameBufferObject, m_PostProcess, 0x41);\n    MARATHON_ASSERT_SIZEOF(SetFrameBufferObject, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetReflectionTextures.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetReflectionTextures : public MyRenderProcess\n    {\n    public:\n        bool m_Flag;\n        xpointer<SoX::Graphics::Texture> m_pReflectionTexture;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetReflectionTextures, m_Flag, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetReflectionTextures, m_pReflectionTexture, 0x3C);\n    MARATHON_ASSERT_SIZEOF(SetReflectionTextures, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetScissorRect.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetScissorRect : public MyRenderProcess\n    {\n    public:\n        be<float> m_X;\n        be<float> m_Y;\n        be<float> m_Width;\n        be<float> m_Height;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetScissorRect, m_X, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetScissorRect, m_Y, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetScissorRect, m_Width, 0x40);\n    MARATHON_ASSERT_OFFSETOF(SetScissorRect, m_Height, 0x44);\n    MARATHON_ASSERT_SIZEOF(SetScissorRect, 0x48);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetScissorTest.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetScissorTest : public MyRenderProcess\n    {\n    public:\n        bool m_Enabled;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetScissorTest, m_Enabled, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetScissorTest, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetScreen.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetScreen : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_CameraIndex;\n        be<float> m_Width;\n        be<float> m_Height;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetScreen, m_CameraIndex, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetScreen, m_Width, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetScreen, m_Height, 0x40);\n    MARATHON_ASSERT_SIZEOF(SetScreen, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetShaderGPRAllocation.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetShaderGPRAllocation : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_VertexShaderCount;\n        be<uint32_t> m_PixelShaderCount;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetShaderGPRAllocation, m_VertexShaderCount, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetShaderGPRAllocation, m_PixelShaderCount, 0x3C);\n    MARATHON_ASSERT_SIZEOF(SetShaderGPRAllocation, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetStencilEnable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetStencilEnable : public MyRenderProcess\n    {\n    public:\n        bool m_FrontEnable;\n        bool m_BackEnable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetStencilEnable, m_FrontEnable, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetStencilEnable, m_BackEnable, 0x39);\n    MARATHON_ASSERT_SIZEOF(SetStencilEnable, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetStencilFunc.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <d3dxb.h>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetStencilFunc : public MyRenderProcess\n    {\n    public:\n        be<D3DXBSTENCILOP> m_StencilMode;\n        be<uint32_t> m_FrontReference;\n        be<uint32_t> m_BackReference;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetStencilFunc, m_StencilMode, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetStencilFunc, m_FrontReference, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetStencilFunc, m_BackReference, 0x40);\n    MARATHON_ASSERT_SIZEOF(SetStencilFunc, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetStencilOp.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <d3dxb.h>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetStencilOp : public MyRenderProcess\n    {\n    public:\n        be<D3DXBSTENCILOP> m_Field38;\n        be<D3DXBSTENCILOP> m_Field3C;\n        be<D3DXBSTENCILOP> m_Field40;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetStencilOp, m_Field38, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetStencilOp, m_Field3C, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetStencilOp, m_Field40, 0x40);\n    MARATHON_ASSERT_SIZEOF(SetStencilOp, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetStencilWriteMask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetStencilWriteMask : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_WriteMask;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetStencilWriteMask, m_WriteMask, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetStencilWriteMask, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetTexture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetTexture : public MyRenderProcess\n    {\n    public:\n        enum AddressMode : uint32_t\n        {\n            AddressMode_Wrap = 1,\n            AddressMode_Clamp = 2,\n            AddressMode_Mirror = 3,\n            AddressMode_Border = 4,\n        };\n\n        enum FilterMode : uint32_t\n        {\n            FilterMode_Point = 1,\n            FilterMode_Linear = 2,\n            FilterMode_LinearMipmap = 3,\n            FilterMode_Trilinear = 4,\n            FilterMode_Anisotropic = 5,\n            FilterMode_Gaussian = 5,\n        };\n\n        be<uint32_t> m_Unknown;\n        xpointer<SoX::Graphics::Texture> m_pTexture;\n        be<AddressMode> m_XAddressMode;\n        be<AddressMode> m_YAddressMode;\n        be<FilterMode> m_FilterMode;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetTexture, m_Unknown, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetTexture, m_pTexture, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetTexture, m_XAddressMode, 0x40);\n    MARATHON_ASSERT_OFFSETOF(SetTexture, m_YAddressMode, 0x44);\n    MARATHON_ASSERT_OFFSETOF(SetTexture, m_FilterMode, 0x48);\n    MARATHON_ASSERT_SIZEOF(SetTexture, 0x4C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetUserClipPlaneEnable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetUserClipPlaneEnable : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_CameraIndex;\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetUserClipPlaneEnable, m_CameraIndex, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetUserClipPlaneEnable, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetViewport.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetViewport : public MyRenderProcess\n    {\n    public:\n        be<float> m_X;\n        be<float> m_Y;\n        be<float> m_Width;\n        be<float> m_Height;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetViewport, m_X, 0x38);\n    MARATHON_ASSERT_OFFSETOF(SetViewport, m_Y, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(SetViewport, m_Width, 0x40);\n    MARATHON_ASSERT_OFFSETOF(SetViewport, m_Height, 0x44);\n    MARATHON_ASSERT_SIZEOF(SetViewport, 0x48);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderAction/SetZMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam::RenderAction\n{\n    class SetZMode : public MyRenderProcess\n    {\n    public:\n        enum ZMode : uint32_t\n        {\n            ZMode_LessEqualOn = 1,\n            ZMode_LessEqualOff = 2,\n            ZMode_LessOn = 3,\n            ZMode_LessOff = 4,\n            ZMode_AlwaysOn = 5,\n            ZMode_AlwaysOff = 6,\n        };\n\n        be<ZMode> m_ZMode;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SetZMode, m_ZMode, 0x38);\n    MARATHON_ASSERT_SIZEOF(SetZMode, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/RenderPostprocess.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam\n{\n    class RenderPostprocess : public MyRenderProcess\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(RenderPostprocess, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SaveDataTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class SaveDataTask : public SoX::Engine::Task\n    {\n    public:\n        xpointer<const char> m_pFileName;\n        MARATHON_INSERT_PADDING(0x24);\n        be<uint32_t> m_Flags;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SaveDataTask, m_pFileName, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(SaveDataTask, m_Flags, 0x74);\n    MARATHON_ASSERT_SIZEOF(SaveDataTask, 0x7C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SaveDataTaskXENON.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class SaveDataTaskXENON : public SaveDataTask\n    {\n    public:\n        enum SaveDataOperation : uint32_t\n        {\n            SaveDataOperation_AlertOverwrite,\n            SaveDataOperation_AlertNoSaveData,\n            SaveDataOperation_AlertSelectDevice,\n            SaveDataOperation_SelectStorageDevice,\n            SaveDataOperation_WriteSaveData,\n            SaveDataOperation_ReadSaveData,\n            SaveDataOperation_AlertSaveFailed,\n            SaveDataOperation_AlertLoadFailed,\n            SaveDataOperation_8,\n            SaveDataOperation_9\n        };\n\n        xpointer<AlertWindowTask> m_pAlertWindowTask;\n        MARATHON_INSERT_PADDING(8);\n        bool m_IsAccessSuccess;\n        xpointer<void> m_pSystemTextBook;\n        be<SaveDataOperation> m_Operation;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SaveDataTaskXENON, m_pAlertWindowTask, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(SaveDataTaskXENON, m_IsAccessSuccess, 0x88);\n    MARATHON_ASSERT_OFFSETOF(SaveDataTaskXENON, m_pSystemTextBook, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(SaveDataTaskXENON, m_Operation, 0x90);\n    MARATHON_ASSERT_SIZEOF(SaveDataTaskXENON, 0x98);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SelectWindowTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/HUDCALLBACK.h>\n\nnamespace Sonicteam\n{\n    class SelectWindowTask : public SoX::Engine::Task, public HUDCALLBACK\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x40);\n        be<uint32_t> m_ChosenIndex;\n        MARATHON_INSERT_PADDING(0x28);\n        be<uint32_t> m_SelectedIndex;\n        MARATHON_INSERT_PADDING(0x14);\n        be<uint32_t> m_BreatheFrame;\n        MARATHON_INSERT_PADDING(0x28);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(SelectWindowTask, m_ChosenIndex, 0x90);\n    MARATHON_ASSERT_OFFSETOF(SelectWindowTask, m_SelectedIndex, 0xBC);\n    MARATHON_ASSERT_OFFSETOF(SelectWindowTask, m_BreatheFrame, 0xD4);\n    MARATHON_ASSERT_SIZEOF(SelectWindowTask, 0x100);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/AI/StateMachine.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::AI\n{\n    template <typename TStateContext>\n    class StateMachine\n    {\n    public:\n        xpointer<void> m_pVftable;\n        xpointer<StateMachine<TStateContext>> m_pState;\n        xpointer<TStateContext> m_pContext;\n\n        template <typename TState>\n        TState* GetState()\n        {\n            return (TState*)m_pState.get();\n        }\n\n        TStateContext* GetContext()\n        {\n            return (TStateContext*)m_pContext.get();\n        }\n\n        template <typename TContext>\n        TContext* GetContext()\n        {\n            return (TContext*)m_pContext.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(StateMachine<void>, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(StateMachine<void>, m_pState, 0x04);\n    MARATHON_ASSERT_OFFSETOF(StateMachine<void>, m_pContext, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/ApplicationXenon.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Application.h>\n\nnamespace Sonicteam::SoX\n{\n    class ApplicationXenon : public Engine::Application\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x174);\n\n        static ApplicationXenon* GetInstance();\n    };\n\n    MARATHON_ASSERT_SIZEOF(ApplicationXenon, 0x180);\n}\n\n#include <Sonicteam/SoX/ApplicationXenon.inl>\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/ApplicationXenon.inl",
    "content": "namespace Sonicteam::SoX\n{\n    inline ApplicationXenon* ApplicationXenon::GetInstance()\n    {\n        return *(xpointer<ApplicationXenon>*)MmGetHostAddress(0x82D3B348);\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Audio/Cue.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Audio/Player.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Audio\n{\n    class Cue : public RefCountObject\n    {\n    public:\n        struct Vftable : RefCountObject::Vftable\n        {\n            MARATHON_INSERT_PADDING(0x18);\n            be<uint32_t> fpSetPause;\n        };\n\n        MARATHON_INSERT_PADDING(0x0C);\n        LinkNode<Cue> m_Field14;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<Player> m_pPlayer;\n\n        template <typename T = Player>\n        T* GetPlayer()\n        {\n            return (T*)m_pPlayer.get();\n        }\n\n        void SetPause(bool isPaused)\n        {\n            GuestToHostFunction<int>(((Vftable*)m_pVftable.get())->fpSetPause, this, isPaused);\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Cue, m_Field14, 0x14);\n    MARATHON_ASSERT_OFFSETOF(Cue, m_pPlayer, 0x28);\n    MARATHON_ASSERT_SIZEOF(Cue, 0x2C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Audio/IAudioEngine.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Audio\n{\n    class IAudioEngine\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IAudioEngine, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IAudioEngine, 4);\n}   \n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Audio/Player.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Audio\n{\n    class Player\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Player, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(Player, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Audio/PlayerImpl.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Audio\n{\n    class PlayerImpl : public Player {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Component.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Object.h>\n\nnamespace Sonicteam::SoX\n{\n    namespace Engine\n    {\n        class DocMode;\n    }\n\n    class Component : public Object\n    {\n    public:\n        xpointer<Engine::DocMode> m_pDocMode;\n        MARATHON_INSERT_PADDING(0x18);\n\n        template <typename T = Engine::DocMode>\n        T* GetDocMode()\n        {\n            return (T*)m_pDocMode.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Component, m_pDocMode, 0x04);\n    MARATHON_ASSERT_SIZEOF(Component, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Engine/Application.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Engine\n{\n    class Application\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Application, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(Application, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Engine/Doc.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/DocMode.h>\n#include <Sonicteam/SoX/Engine/Task.h>\n\nnamespace Sonicteam::SoX::Engine\n{\n    class Doc\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<DocMode> m_pDocMode;\n        xpointer<Task> m_pRootTask;\n        xpointer<Task> m_pRootGTask;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<Task> m_pDocModeExecutor;\n        MARATHON_INSERT_PADDING(0x3C);\n\n        template <typename T = DocMode>\n        inline T* GetDocMode()\n        {\n            return (T*)m_pDocMode.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Doc, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Doc, m_pDocMode, 0x08);\n    MARATHON_ASSERT_OFFSETOF(Doc, m_pRootTask, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(Doc, m_pRootGTask, 0x10);\n    MARATHON_ASSERT_OFFSETOF(Doc, m_pDocModeExecutor, 0x1C);\n    MARATHON_ASSERT_SIZEOF(Doc, 0x5C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Engine/DocMode.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Task.h>\n\nnamespace Sonicteam::SoX::Engine\n{\n    class DocMode : public Task\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(DocMode, 0x50);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Engine/RenderProcess.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Engine\n{\n    class RenderScheduler;\n    \n    class RenderProcess\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpFunc04;\n            be<uint32_t> fpFunc08;\n            be<uint32_t> fpPerformProcess;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        xpointer<uint32_t> m_Flag1; // Or Type?\n        xpointer<void> m_pGTask;\n        be<uint32_t> m_Flag2;\n        MARATHON_INSERT_PADDING(0x8);\n        be<float> m_Field18;\n        MARATHON_INSERT_PADDING(0x4);\n        xpointer<RenderScheduler> m_pRenderScheduler;\n        MARATHON_INSERT_PADDING(0xC);\n    };\n\n    MARATHON_ASSERT_SIZEOF(RenderProcess, 0x30);\n    MARATHON_ASSERT_OFFSETOF(RenderProcess, m_Flag1, 4);\n    MARATHON_ASSERT_OFFSETOF(RenderProcess, m_pGTask, 8);\n    MARATHON_ASSERT_OFFSETOF(RenderProcess, m_Flag2, 0xC);\n    MARATHON_ASSERT_OFFSETOF(RenderProcess, m_Field18, 0x18);\n    MARATHON_ASSERT_OFFSETOF(RenderProcess, m_pRenderScheduler, 0x20);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Engine/Task.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Component.h>\n#include <Sonicteam/SoX/LinkNode.h>\n#include <Sonicteam/SoX/MessageReceiver.h>\n\nnamespace Sonicteam::SoX::Engine\n{\n    class Doc;\n\n    class Task : public Component, public MessageReceiver\n    {\n    public:\n        be<uint32_t> m_Flags;\n        be<uint32_t> m_Timestamp;\n        xpointer<Task> m_pPrevSibling;\n        xpointer<Task> m_pNextSibling;\n        xpointer<Task> m_pDependency;\n        xpointer<Task> m_pDependencies;\n        xpointer<Doc> m_pDoc;\n        LinkNode<Task> m_lnTask;\n\n        Task* GetFirstDependency() const \n        {\n            if (!m_pDependencies)\n                return nullptr;\n\n            auto pCurrent = m_pDependencies.get();\n\n            while (pCurrent->m_pPrevSibling && pCurrent->m_pPrevSibling != m_pDependencies.get())\n                pCurrent = pCurrent->m_pPrevSibling.get();\n\n            return pCurrent;\n        }\n\n        Task* GetLastDependency() const\n        {\n            return m_pDependencies.get();\n        }\n\n        template <typename T = Doc>\n        T* GetDoc()\n        {\n            return (T*)m_pDoc.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Task, m_Flags, 0x24);\n    MARATHON_ASSERT_OFFSETOF(Task, m_Timestamp, 0x28);\n    MARATHON_ASSERT_OFFSETOF(Task, m_pPrevSibling, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(Task, m_pNextSibling, 0x30);\n    MARATHON_ASSERT_OFFSETOF(Task, m_pDependency, 0x34);\n    MARATHON_ASSERT_OFFSETOF(Task, m_pDependencies, 0x38);\n    MARATHON_ASSERT_OFFSETOF(Task, m_pDoc, 0x3C);\n    MARATHON_ASSERT_OFFSETOF(Task, m_lnTask, 0x40);\n    MARATHON_ASSERT_SIZEOF(Task, 0x4C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Device.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Math/Matrix.h>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class Device\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x3C);\n        Math::Matrix4x4 m_Field40;\n        be<float> m_Field80;\n        MARATHON_INSERT_PADDING(0x4C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Device, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Device, m_Field40, 0x40);\n    MARATHON_ASSERT_OFFSETOF(Device, m_Field80, 0x80);\n    MARATHON_ASSERT_SIZEOF(Device, 0xD0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Frame.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class Frame : public RefCountObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x18); // SimpleNode\n        MARATHON_INSERT_PADDING(0x2C);\n    };\n\n    MARATHON_ASSERT_SIZEOF(Frame, 0x4C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/FrameGP.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Frame.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class FrameGP : public Frame\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x84);\n        stdx::string m_Name;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(FrameGP, m_Name, 0xD0);\n    MARATHON_ASSERT_SIZEOF(FrameGP, 0xF0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/FrameObserver.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/FrameGP.h>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class FrameObserver\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<FrameObserver> m_pParent;\n        xpointer<FrameGP> m_pFrameGP;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(FrameObserver, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(FrameObserver, m_pParent, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(FrameObserver, m_pFrameGP, 0x10);\n    MARATHON_ASSERT_SIZEOF(FrameObserver, 0x14);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Technique.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class Technique : public RefCountObject\n    {\n    public:\n        xpointer<void> m_pShader;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Technique, m_pShader, 0x08);\n    MARATHON_ASSERT_SIZEOF(Technique, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/TechniqueFXL.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Technique.h>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class TechniqueFXL : public Technique\n    {\n    public:\n        xpointer<void> m_pShader;\n        MARATHON_INSERT_PADDING(0x10);\n        xpointer<TechniqueFXL> m_pParent;\n        MARATHON_INSERT_PADDING(4);\n        stdx::string m_TechniqueName;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TechniqueFXL, m_pShader, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(TechniqueFXL, m_pParent, 0x20);\n    MARATHON_ASSERT_OFFSETOF(TechniqueFXL, m_TechniqueName, 0x28);\n    MARATHON_ASSERT_SIZEOF(TechniqueFXL, 0x44);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Texture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/IResource.h>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class Texture : public IResource {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Transforms.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    class Transforms\n    {\n    public:\n        struct Vftable\n        {\n            MARATHON_INSERT_PADDING(4);\n            be<uint32_t> fpComputeProjectionMatrix;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        MARATHON_INSERT_PADDING(0xEC);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Transforms, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(Transforms, 0xF0);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Vertex.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Graphics\n{\n    struct Vertex\n    {\n        be<float> X;\n        be<float> Y;\n        MARATHON_INSERT_PADDING(0x10);\n        be<uint32_t> Colour; // ARGB8888\n        be<float> U;\n        be<float> V;\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Vertex, X, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Vertex, Y, 0x04);\n    MARATHON_ASSERT_OFFSETOF(Vertex, Colour, 0x18);\n    MARATHON_ASSERT_OFFSETOF(Vertex, U, 0x1C);\n    MARATHON_ASSERT_OFFSETOF(Vertex, V, 0x20);\n    MARATHON_ASSERT_SIZEOF(Vertex, 0x3C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Xenon/DeviceXenon.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Device.h>\n\nnamespace Sonicteam::SoX::Graphics::Xenon\n{\n    class DeviceXenon : public Device\n    {\n    public:\n        MARATHON_INSERT_PADDING(0xC0);\n    };\n\n    MARATHON_ASSERT_SIZEOF(DeviceXenon, 0x190);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Graphics/Xenon/TextureXenon.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Texture.h>\n\nnamespace Sonicteam::SoX::Graphics::Xenon\n{\n    class TextureXenon : public Texture\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1C);\n        be<uint32_t> m_Width;\n        be<uint32_t> m_Height;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TextureXenon, m_Width, 0x80);\n    MARATHON_ASSERT_OFFSETOF(TextureXenon, m_Height, 0x84);\n    MARATHON_ASSERT_SIZEOF(TextureXenon, 0x8C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/IResource.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <stdx/string.h>\n\nnamespace Sonicteam::SoX\n{\n    class IResource : public RefCountObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        stdx::string m_FilePath;\n        MARATHON_INSERT_PADDING(0x3C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IResource, m_FilePath, 0x0C);\n    MARATHON_ASSERT_SIZEOF(IResource, 0x64);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/IResource2.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/IResource.h>\n#include <Sonicteam/SoX/IResourceMgr.h>\n\nnamespace Sonicteam::SoX\n{\n    template <class TRes = IResource, class TMgr = IResourceMgr>\n    class IResource2 : public IResource {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/IResourceMgr.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX\n{\n    class IResourceMgr\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpMakeResource;\n            MARATHON_INSERT_PADDING(4);\n        };\n\n        xpointer<Vftable> m_pVftable;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(IResourceMgr, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(IResourceMgr, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Input/Manager.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Input\n{\n    enum KeyState\n    {\n        KeyState_DPadUp = 0x40,\n        KeyState_DPadDown = 0x80,\n        KeyState_DPadLeft = 0x100,\n        KeyState_DPadRight = 0x200,\n        KeyState_Start = 0x400,\n        KeyState_Select = 0x800,\n        KeyState_LeftStick = 0x10000,\n        KeyState_RightStick = 0x20000,\n        KeyState_LeftBumper = 0x1000,\n        KeyState_RightBumper = 0x2000,\n        KeyState_A = 0x01,\n        KeyState_B = 0x02,\n        KeyState_X = 0x08,\n        KeyState_Y = 0x10,\n        KeyState_LeftTrigger = 0x4000,\n        KeyState_RightTrigger = 0x8000,\n    };\n\n    struct PadState\n    {\n        be<uint32_t> LastButtons;\n        be<uint32_t> InvertedLastButtons;\n        be<uint32_t> PressedButtons;\n        be<uint32_t> ReleasedButtons;\n        be<float> LeftStickHorizontal;\n        be<float> LeftStickVertical;\n        be<short> LeftStickHorizontalS16;\n        be<short> LeftStickVerticalS16;\n        be<float> RightStickHorizontal;\n        be<float> RightStickVertical;\n        be<short> RightStickHorizontalS16;\n        be<short> RightStickVerticalS16;\n\n        bool IsDown(const KeyState in_Keys) const;\n        bool IsPressed(const KeyState in_Keys) const;\n        bool IsReleased(const KeyState in_Keys) const;\n    };\n\n    class Manager\n    {\n    public:\n        be<uint32_t> m_PadID;\n        MARATHON_INSERT_PADDING(0x0C);\n        PadState m_PadState;\n        MARATHON_INSERT_PADDING(0x28);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(PadState, LastButtons, 0x00);\n    MARATHON_ASSERT_OFFSETOF(PadState, InvertedLastButtons, 0x04);\n    MARATHON_ASSERT_OFFSETOF(PadState, PressedButtons, 0x08);\n    MARATHON_ASSERT_OFFSETOF(PadState, ReleasedButtons, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(PadState, LeftStickHorizontal, 0x10);\n    MARATHON_ASSERT_OFFSETOF(PadState, LeftStickVertical, 0x14);\n    MARATHON_ASSERT_OFFSETOF(PadState, LeftStickHorizontalS16, 0x18);\n    MARATHON_ASSERT_OFFSETOF(PadState, LeftStickVerticalS16, 0x1A);\n    MARATHON_ASSERT_OFFSETOF(PadState, RightStickHorizontal, 0x1C);\n    MARATHON_ASSERT_OFFSETOF(PadState, RightStickVertical, 0x20);\n    MARATHON_ASSERT_OFFSETOF(PadState, RightStickHorizontalS16, 0x24);\n    MARATHON_ASSERT_OFFSETOF(PadState, RightStickVerticalS16, 0x26);\n\n    MARATHON_ASSERT_OFFSETOF(Manager, m_PadID, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Manager, m_PadState, 0x10);\n}\n\n#include \"Manager.inl\"\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Input/Manager.inl",
    "content": "namespace Sonicteam::SoX::Input\n{\n    inline bool PadState::IsDown(const KeyState in_Keys) const\n    {\n        return (LastButtons & in_Keys) == in_Keys;\n    }\n\n    inline bool PadState::IsPressed(const KeyState in_Keys) const\n    {\n        return (PressedButtons & in_Keys) == in_Keys;\n    }\n\n    inline bool PadState::IsReleased(const KeyState in_Keys) const\n    {\n        return (ReleasedButtons & in_Keys) == in_Keys;\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/LinkNode.h",
    "content": "#pragma once\n\nnamespace Sonicteam::SoX\n{\n    template <typename T>\n    class ILinkNode\n    {\n    public:\n        xpointer<T> m_pPrev;\n        xpointer<T> m_pNext;\n    };\n\n    template <typename T>\n    class LinkNode : public ILinkNode<LinkNode<T>>\n    {\n    public:\n        xpointer<T> m_pThis;\n    };\n\n    template <typename T>\n    class LinkRef\n    {\n    public:\n        xpointer<T> m_pElement;\n        LinkNode<LinkRef> m_lnElement;\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Math/Matrix.h",
    "content": "#pragma once\n\n#include \"Marathon.inl\"\n\nnamespace Sonicteam::SoX::Math\n{\n    class Matrix4x4\n    {\n    public:\n        be<float> M[4][4];\n\n        Matrix4x4() {}\n\n        Matrix4x4(Matrix4x4& other)\n        {\n            for (int i = 0; i < 4; i++)\n            {\n                for (int j = 0; j < 4; j++)\n                    M[i][j] = other.M[i][j];\n            }\n        }\n\n        Matrix4x4 Multiply(const Matrix4x4& other) const\n        {\n            Matrix4x4 result{};\n\n            for (int i = 0; i < 4; i++)\n            {\n                for (int j = 0; j < 4; j++)\n                {\n                    result.M[i][j] = 0.0f;\n\n                    for (int k = 0; k < 4; k++)\n                        result.M[i][j] = result.M[i][j] + (M[i][k] * other.M[k][j]);\n                }\n            }\n\n            return result;\n        }\n\n        Matrix4x4 operator*(Matrix4x4 other)\n        {\n            return Multiply(other);\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Math/Quaternion.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Math\n{\n    class alignas(16) Quaternion\n    {\n    public:\n        be<float> X;\n        be<float> Y;\n        be<float> Z;\n        be<float> W;\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Math/Vector.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Math\n{\n    class Vector2\n    {\n    public:\n        be<float> X;\n        be<float> Y;\n    };\n\n    class Vector3\n    {\n    public:\n        be<float> X;\n        be<float> Y;\n        be<float> Z;\n    };\n\n    class alignas(16) Vector\n    {\n    public:\n        be<float> X;\n        be<float> Y;\n        be<float> Z;\n        be<float> W;\n\n        float DistanceTo(const Vector& other) const\n        {\n            float dx = static_cast<float>(X) - static_cast<float>(other.X);\n            float dy = static_cast<float>(Y) - static_cast<float>(other.Y);\n            float dz = static_cast<float>(Z) - static_cast<float>(other.Z);\n\n            return std::sqrt(dx * dx + dy * dy + dz * dz);\n        }\n\n        Vector operator+(float addend)\n        {\n            return { X + addend, Y + addend, Z + addend, W + addend };\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Message.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX\n{\n    struct IMessage\n    {\n        be<uint32_t> ID{};\n\n        IMessage() {}\n        IMessage(const uint32_t id) : ID(id) {}\n    };\n\n    template <const uint32_t id>\n    struct Message : IMessage\n    {\n        Message()\n        {\n            ID = id;\n        }\n\n        static const uint32_t GetID()\n        {\n            return id;\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/MessageReceiver.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Message.h>\n\nnamespace Sonicteam::SoX\n{\n    class MessageReceiver\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n            be<uint32_t> fpProcessMessage;\n        };\n\n        xpointer<Vftable> m_pVftable;\n\n        void* Destroy(uint8_t flags = 1)\n        {\n            return GuestToHostFunction<void*>(m_pVftable->fpDestroy, this, flags);\n        }\n\n        bool ProcessMessage(IMessage* pMessage)\n        {\n            return GuestToHostFunction<bool>(m_pVftable->fpProcessMessage, this, pMessage);\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(MessageReceiver, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(MessageReceiver, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Object.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX\n{\n    class Object\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpGetName;\n        };\n\n        xpointer<Vftable> m_pVftable;\n\n        const char* GetName() const\n        {\n            return GuestToHostFunction<const char*>(m_pVftable->fpGetName.get());\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Object, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(Object, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Entity.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Task.h>\n#include <Sonicteam/SoX/MessageReceiver.h>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/SoX/Physics/Shape.h>\n#include <Sonicteam/SoX/Physics/World.h>\n#include <Sonicteam/SoX/Physics/PhantomListener.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class Entity : public MessageReceiver, public RefCountObject\n    {\n    public:\n        struct Vftable : public MessageReceiver::Vftable\n        {\n            MARATHON_INSERT_PADDING(0x38);\n            be<uint32_t> fpInitializeToWorld;\n            MARATHON_INSERT_PADDING(0x18);\n            be<uint32_t> fpSetPhantomListener;\n        };\n\n        MARATHON_INSERT_PADDING(0x10);\n        RefSharedPointer<Shape> m_spPhantomListener;\n        xpointer<MessageReceiver> m_pReceiver;\n        RefSharedPointer<Shape> m_spShape;\n\n        void InitializeToWorld(RefSharedPointer<World>& world)\n        {\n            auto vft = static_cast<Vftable*>(MessageReceiver::m_pVftable.get());\n            GuestToHostFunction<void>(vft->fpInitializeToWorld, this, &world);\n        }\n\n        void SetPhantomListener(RefSharedPointer<PhantomListener>& phantomListener)\n        {\n            auto vft = static_cast<Vftable*>(MessageReceiver::m_pVftable.get());\n            GuestToHostFunction<void>(vft->fpSetPhantomListener, this, &phantomListener);\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Entity, m_spPhantomListener, 0x1C);\n    MARATHON_ASSERT_OFFSETOF(Entity, m_pReceiver, 0x20);\n    MARATHON_ASSERT_OFFSETOF(Entity, m_spShape, 0x24);\n    MARATHON_ASSERT_SIZEOF(Entity, 0x28);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Havok/EntityHavok.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefSharedPointer.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n#include <Sonicteam/SoX/Math/Quaternion.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/SoX/Physics/Havok/WorldHavok.h>\n#include <hk330/hkpRigidBody.h>\n\nnamespace Sonicteam::SoX::Physics::Havok\n{\n    class EntityHavok\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(0x0C);\n        xpointer<hk330::hkpRigidBody> m_pRigidBody;\n        RefSharedPointer<WorldHavok> m_spWorldHavok;\n        Math::Quaternion m_Rotation;\n        Math::Vector m_Translation;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(EntityHavok, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(EntityHavok, m_pRigidBody, 0x10);\n    MARATHON_ASSERT_OFFSETOF(EntityHavok, m_spWorldHavok, 0x14);\n    MARATHON_ASSERT_OFFSETOF(EntityHavok, m_Rotation, 0x20);\n    MARATHON_ASSERT_OFFSETOF(EntityHavok, m_Translation, 0x30);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Havok/EntityHavokImp.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Physics/Havok/WorldHavok.h>\n#include <Sonicteam/SoX/Physics/Havok/EntityHavok.h>\n\nnamespace Sonicteam::SoX::Physics::Havok\n{\n    template<typename T>\n    class EntityHavokImp : public T, public EntityHavok {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Havok/PhantomHavok.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Physics/Havok/EntityHavokImp.h>\n#include <Sonicteam/SoX/Physics/Phantom.h>\n\nnamespace Sonicteam::SoX::Physics::Havok\n{\n    class PhantomHavok : public EntityHavokImp<Phantom> {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Havok/WorldHavok.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpWorld.h>\n#include <Sonicteam/SoX/Physics/World.h>\n\nnamespace Sonicteam::SoX::Physics::Havok\n{\n    class WorldHavok : public World\n    {\n    public:\n        xpointer<hk330::hkpWorld> m_pWorld;\n        MARATHON_INSERT_PADDING(0x14);\n        bool m_IsDynamicUpdateRate;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(WorldHavok, m_pWorld, 0x08);\n    MARATHON_ASSERT_OFFSETOF(WorldHavok, m_IsDynamicUpdateRate, 0x20);\n    MARATHON_ASSERT_SIZEOF(WorldHavok, 0x24);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/IntersectEvent.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class IntersectEvent {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/IntersectListener.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class IntersectListener : public SoX::RefCountObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_SIZEOF(IntersectListener, 0x14);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Phantom.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Physics/Entity.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class Phantom : public Entity {};\n\n    MARATHON_ASSERT_SIZEOF(Phantom, 0x28);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/PhantomListener.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class PhantomListener : public RefCountObject {};\n\n    MARATHON_ASSERT_SIZEOF(PhantomListener, 8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/Shape.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class Shape : public RefCountObject \n    {\n    public:\n        struct Vftable : public RefCountObject::Vftable\n        {\n            be<uint32_t> fpVFunction04; // (__out VECTOR* u1, __out VECTOR* u2)\n            be<uint32_t> fpInitializeVolume;\n        };\n\n        be<uint32_t> m_ShapeType;\n        be<float> m_ShapeVolume; // w * h * ... * FLT_MIN\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Shape, m_ShapeType, 0x08);\n    MARATHON_ASSERT_OFFSETOF(Shape, m_ShapeVolume, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/ShapeCastEvent.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class ShapeCastEvent {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/ShapeCastListener.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class ShapeCastListener : public SoX::RefCountObject {};\n\n    MARATHON_ASSERT_SIZEOF(ShapeCastListener, 8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Physics/World.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Physics\n{\n    class World : public RefCountObject {};\n\n    MARATHON_ASSERT_SIZEOF(World, 8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/RefCountObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX\n{\n    class RefCountObject\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpDestroy;\n        };\n\n        xpointer<Vftable> m_pVftable;\n        be<uint32_t> m_ReferenceCount;\n\n        void* Destroy(uint32_t flag = 1)\n        {\n            return GuestToHostFunction<void*>(m_pVftable->fpDestroy, this, flag);\n        }\n\n        void Release(uint32_t flag = 1)\n        {\n            m_ReferenceCount = m_ReferenceCount - 1;\n\n            if (!m_ReferenceCount.get())\n                Destroy(flag);\n        }\n\n        inline void AddReference()\n        {\n            m_ReferenceCount = m_ReferenceCount + 1;\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(RefCountObject, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(RefCountObject, m_ReferenceCount, 0x04);\n    MARATHON_ASSERT_SIZEOF(RefCountObject, 8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/RefSharedPointer.h",
    "content": "#pragma once\n\n#include <Marathon.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX\n{\n    template <typename T = RefCountObject>\n    class RefSharedPointer\n    {\n    private:\n        xpointer<T> m_ptr;\n\n    public:\n        explicit RefSharedPointer(T* value) : m_ptr(value)\n        {\n            if (m_ptr.get())\n                m_ptr->AddRef();\n        }\n\n        explicit RefSharedPointer(xpointer<T> value) : m_ptr(value)\n        {\n            if (m_ptr.get())\n                m_ptr->AddRef();\n        }\n\n        RefSharedPointer(const RefSharedPointer& other) : m_ptr(other.m_ptr)\n        {\n            if (m_ptr.get())\n                m_ptr->AddRef();\n        }\n\n        RefSharedPointer(RefSharedPointer&& other) noexcept : m_ptr(std::move(other.m_ptr))\n        {\n            other.m_ptr = nullptr;\n        }\n\n        ~RefSharedPointer()\n        {\n            if (m_ptr.get())\n                m_ptr->Release();\n        }\n\n        RefSharedPointer& operator=(const RefSharedPointer& other)\n        {\n            if (this != &other) \n            {\n                reset();\n\n                m_ptr = other.m_ptr;\n\n                if (m_ptr.get())\n                    m_ptr->AddRef();\n            }\n\n            return *this;\n        }\n\n        RefSharedPointer& operator=(RefSharedPointer&& other) noexcept\n        {\n            if (this != &other) \n            {\n                reset();\n\n                m_ptr = std::move(other.m_ptr);\n\n                other.m_ptr = nullptr;\n            }\n\n            return *this;\n        }\n\n        void reset()\n        {\n            if (m_ptr.get())\n                m_ptr->Release();\n\n            m_ptr = 0;\n        }\n\n        T* get() const noexcept\n        {\n            return m_ptr.get();\n        }\n\n        T* operator->() const noexcept\n        {\n            return m_ptr.get();\n        }\n\n        T& operator*() const noexcept\n        {\n            return *m_ptr.get();\n        }\n\n        explicit operator bool() const noexcept\n        {\n            return m_ptr.get() != nullptr;\n        }\n\n        bool operator==(const RefSharedPointer& other) const noexcept\n        {\n            return m_ptr.get() == other.m_ptr.get();\n        }\n\n        bool operator!=(const RefSharedPointer& other) const noexcept\n        {\n            return !(*this == other);\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Scenery/Camera.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX::Scenery\n{\n    class Camera\n    {\n    public:\n        struct Vftable\n        {\n            MARATHON_INSERT_PADDING(0x3C);\n            be<uint32_t> fpSetFar;\n            be<uint32_t> fpSetFOV;\n            be<uint32_t> fpSetViewMatrix;\n            MARATHON_INSERT_PADDING(4);\n            be<uint32_t> fpSetAspectRatio;\n            MARATHON_INSERT_PADDING(0x3C);\n            be<uint32_t> fpUpdate;\n        };\n\n        xpointer<Vftable> m_pVftable;\n\n        int SetFar(double zFar)\n        {\n            return GuestToHostFunction<int>(m_pVftable->fpSetFar.get(), this, zFar);\n        }\n\n        int SetFOV(double fov)\n        {\n            return GuestToHostFunction<int>(m_pVftable->fpSetFOV.get(), this, fov);\n        }\n\n        int SetAspectRatio(float width, float height)\n        {\n            struct AspectRatio\n            {\n                be<float> Width;\n                be<float> Height;\n            };\n\n            guest_stack_var<AspectRatio> aspectRatio(width, height);\n\n            return GuestToHostFunction<int>(m_pVftable->fpSetAspectRatio.get(), this, aspectRatio.get());\n        }\n\n        int Update()\n        {\n            struct Unknown\n            {\n                MARATHON_INSERT_PADDING(0x40);\n            };\n\n            guest_stack_var<Unknown> unknown;\n\n            return GuestToHostFunction<int>(m_pVftable->fpUpdate.get(), unknown.get(), this);\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Camera, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(Camera, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Scenery/CameraEventCallback.h",
    "content": "#pragma once\n\n#include <Marathon.h>\n\nnamespace Sonicteam::SoX::Scenery\n{\n    class CameraEventCallback\n    {\n    public:\n        xpointer<void> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CameraEventCallback, m_pVftable, 0x00);\n    MARATHON_ASSERT_SIZEOF(CameraEventCallback, 4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Scenery/CameraImp.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/FrameObserver.h>\n#include <Sonicteam/SoX/Math/Matrix.h>\n#include <Sonicteam/SoX/Math/Quaternion.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n#include <Sonicteam/MyTransforms.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam::SoX::Scenery\n{\n    class CameraImp : public Camera\n    {\n    public:\n        class frameObserver : public Graphics::FrameObserver\n        {\n        public:\n            xpointer<CameraImp> m_pCameraImp;\n            be<uint32_t> m_Field18;\n            MARATHON_INSERT_PADDING(0x14);\n            SoX::Math::Matrix4x4 m_Field30;\n            SoX::Math::Matrix4x4 m_Field70;\n            SoX::Math::Quaternion m_FieldB0;\n            SoX::Math::Vector m_FieldC0;\n            MARATHON_INSERT_PADDING(0x20);\n            xpointer<void> m_pWorldIntersectionStandard;\n            MARATHON_INSERT_PADDING(0x10);\n            xpointer<MyTransforms> m_pMyTransforms;\n            MARATHON_INSERT_PADDING(8);\n        };\n\n        stdx::string m_Name;\n        be<float> m_FOV;\n        be<float> m_AspectRatioWidth;\n        be<float> m_AspectRatioHeight;\n        be<float> m_Near;\n        be<float> m_Far;\n        xpointer<void> m_pWorldImp;\n        MARATHON_INSERT_PADDING(0x18);\n        SoX::Math::Matrix4x4 m_ViewMatrix;\n        SoX::Math::Matrix4x4 m_Field90;\n        SoX::Math::Matrix4x4 m_FieldD0;\n        frameObserver m_FrameObserver;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_pCameraImp, 0x14);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_Field18, 0x18);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_Field30, 0x30);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_Field70, 0x70);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_FieldB0, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_FieldC0, 0xC0);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_pWorldIntersectionStandard, 0xF0);\n    MARATHON_ASSERT_OFFSETOF(CameraImp::frameObserver, m_pMyTransforms, 0x104);\n    MARATHON_ASSERT_SIZEOF(CameraImp::frameObserver, 0x110);\n\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_Name, 0x04);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_FOV, 0x20);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_AspectRatioWidth, 0x24);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_AspectRatioHeight, 0x28);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_Near, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_Far, 0x30);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_pWorldImp, 0x34);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_ViewMatrix, 0x50);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_Field90, 0x90);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_FieldD0, 0xD0);\n    MARATHON_ASSERT_OFFSETOF(CameraImp, m_FrameObserver, 0x110);\n    MARATHON_ASSERT_SIZEOF(CameraImp, 0x220);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Scenery/Drawable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Scenery/CameraEventCallback.h>\n#include <Sonicteam/SoX/RefCountObject.h>\n\nnamespace Sonicteam::SoX::Scenery\n{\n    class Drawable : public RefCountObject, public CameraEventCallback\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x64);\n    };\n\n    MARATHON_ASSERT_SIZEOF(Drawable, 0x70);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/SoX/Thread.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::SoX\n{\n    class Thread\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<Thread> m_pParent;\n        be<uint32_t> m_EventHandleA;\n        be<uint32_t> m_EventHandleB;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> m_ThreadHandle;\n        bool m_Field20;\n        MARATHON_INSERT_PADDING(3);\n        be<float> m_DeltaTime;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<const char> m_pName;\n        MARATHON_INSERT_PADDING(4);\n        bool m_Field38;\n        bool m_Field39;\n        MARATHON_INSERT_PADDING(6);\n        xpointer<void> m_pContext;\n        MARATHON_INSERT_PADDING(4);\n\n        template <typename T>\n        T* GetContext()\n        {\n            return (T*)m_pContext.get();\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Thread, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_pParent, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_EventHandleA, 0x10);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_EventHandleB, 0x14);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_ThreadHandle, 0x1C);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_Field20, 0x20);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_DeltaTime, 0x24);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_pName, 0x30);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_Field38, 0x38);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_Field39, 0x39);\n    MARATHON_ASSERT_OFFSETOF(Thread, m_pContext, 0x40);\n    MARATHON_ASSERT_SIZEOF(Thread, 0x48);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/StdImageFilters/BurnoutBlurFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/StdImageFilters/SingleTechniqueFilter.h>\n\nnamespace Sonicteam::StdImageFilters\n{\n    class BurnoutBlurFilter : public SingleTechniqueFilter\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x14);\n        be<float> m_Magnitude;\n        xpointer<SoX::Graphics::TechniqueFXL> m_p1xBlurTechnique;\n        xpointer<SoX::Graphics::TechniqueFXL> m_p4xBlurTechnique;\n        xpointer<SoX::Graphics::TechniqueFXL> m_p8xBlurTechnique;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(BurnoutBlurFilter, m_Magnitude, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(BurnoutBlurFilter, m_p1xBlurTechnique, 0x30);\n    MARATHON_ASSERT_OFFSETOF(BurnoutBlurFilter, m_p4xBlurTechnique, 0x34);\n    MARATHON_ASSERT_OFFSETOF(BurnoutBlurFilter, m_p8xBlurTechnique, 0x38);\n    MARATHON_ASSERT_SIZEOF(BurnoutBlurFilter, 0x40);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/StdImageFilters/SingleTechniqueFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam::StdImageFilters\n{\n    class SingleTechniqueFilter : public ImageFilter\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x10);\n    };\n\n    MARATHON_ASSERT_SIZEOF(SingleTechniqueFilter, 0x18);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/System/CreateStatic.h",
    "content": "#pragma once\n\nnamespace Sonicteam::System\n{\n    template <typename T, uint32_t fpCreator>\n    class CreateStatic\n    {\n    public:\n        static T* Create()\n        {\n            return GuestToHostFunction<T*>(fpCreator);\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/System/Diagnostics/Performance.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/System/Singleton.h>\n#include <Sonicteam/System/CreateStatic.h>\n\nnamespace Sonicteam::System::Diagnostics\n{\n    class Performance : public Singleton<Performance, 0x82D3B210, CreateStatic<Performance, 0x82581C88>>\n    {\n    public:\n        be<uint64_t> m_LastFrequency;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(Performance, m_LastFrequency, 0x00);\n    MARATHON_ASSERT_SIZEOF(Performance, 8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/System/Singleton.h",
    "content": "#pragma once\n\nnamespace Sonicteam::System\n{\n    template <typename T, const uint32_t pSingleton, typename TCreator>\n    class Singleton\n    {\n        inline static TCreator ms_Creator{};\n\n    public:\n        static T* GetInstance()\n        {\n            auto pInstance = (xpointer<T>*)g_memory.Translate(pSingleton);\n\n            if (!pInstance->ptr.get())\n                *pInstance = ms_Creator.Create();\n\n            return *pInstance;\n        }\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TextBook.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/IResource2.h>\n#include <Sonicteam/TextBookMgr.h>\n#include <Sonicteam/TextCard.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class TextBook : public SoX::IResource2<TextBook, TextBookMgr>\n    {\n    public:\n        struct Entry\n        {\n            xpointer<Entry> Field00;\n            xpointer<Entry> Previous;\n            xpointer<Entry> Next;\n            stdx::string Name;\n            boost::shared_ptr<TextCard> spTextCard;\n            MARATHON_INSERT_PADDING(1);\n            bool Field31;\n            MARATHON_INSERT_PADDING(2);\n        };\n\n        MARATHON_INSERT_PADDING(4);\n        boost::shared_ptr<uint8_t> m_spResource;\n        stdx::string m_Name;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<Entry> m_pRootEntry;\n        be<uint32_t> m_EntryCount;\n\n        const char* FindName(const uint16_t* pText) const\n        {\n            auto pCardTable = m_spResource.get() + 0x2C;\n\n            for (size_t i = 0; i < m_EntryCount; i++)\n            {\n                auto pCard = pCardTable + (i * 0x0C);\n                auto pCardName = *(xpointer<const char>*)pCard;\n                auto pCardText = *(xpointer<const uint16_t>*)(pCard + 4);\n\n                if (!strcmpU16(pCardText, pText))\n                    continue;\n\n                return pCardName;\n            }\n\n            return nullptr;\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TextBook::Entry, Field00, 0x00);\n    MARATHON_ASSERT_OFFSETOF(TextBook::Entry, Previous, 0x04);\n    MARATHON_ASSERT_OFFSETOF(TextBook::Entry, Next, 0x08);\n    MARATHON_ASSERT_OFFSETOF(TextBook::Entry, Name, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(TextBook::Entry, spTextCard, 0x28);\n    MARATHON_ASSERT_OFFSETOF(TextBook::Entry, Field31, 0x31);\n    MARATHON_ASSERT_SIZEOF(TextBook::Entry, 0x34);\n\n    MARATHON_ASSERT_OFFSETOF(TextBook, m_spResource, 0x68);\n    MARATHON_ASSERT_OFFSETOF(TextBook, m_Name, 0x70);\n    MARATHON_ASSERT_OFFSETOF(TextBook, m_pRootEntry, 0x90);\n    MARATHON_ASSERT_OFFSETOF(TextBook, m_EntryCount, 0x94);\n    MARATHON_ASSERT_SIZEOF(TextBook, 0x98);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TextBookMgr.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/System/CreateStatic.h>\n#include <Sonicteam/System/Singleton.h>\n\nnamespace Sonicteam\n{\n    class TextBookMgr : public SoX::IResourceMgr, public System::Singleton<TextBookMgr, 0x82D35ED8, System::CreateStatic<TextBookMgr, 0x82162D70>> {};\n\n    MARATHON_ASSERT_SIZEOF(TextBookMgr, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TextCard.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class TextCard\n    {\n    public:\n        boost::shared_ptr<uint8_t> m_spResource;\n        xpointer<const uint16_t> m_pText;\n        xpointer<const char> m_pVariables;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TextCard, m_spResource, 0x00);\n    MARATHON_ASSERT_OFFSETOF(TextCard, m_pText, 0x08);\n    MARATHON_ASSERT_OFFSETOF(TextCard, m_pVariables, 0x0C);\n    MARATHON_ASSERT_SIZEOF(TextCard, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TextEntity.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Graphics/Vertex.h>\n#include <Sonicteam/CsdLink.h>\n#include <stdx/wstring.h>\n\nnamespace Sonicteam\n{\n    class TextFont;\n    class TextBoard;\n\n    class TextEntity : public CsdLink\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        boost::shared_ptr<TextFont> m_spTextFont;\n        boost::shared_ptr<TextBoard> m_spTextBoard;\n        MARATHON_INSERT_PADDING(4);\n        be<float> m_X;\n        be<float> m_Y;\n        MARATHON_INSERT_PADDING(8);\n        stdx::wstring m_Text;\n        stdx::wstring m_Field5C;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<const char> m_pVariables;\n        stdx::wstring m_Field80;\n        MARATHON_INSERT_PADDING(0x14);\n        be<float> m_Width;\n        be<float> m_FieldB4;\n        be<float> m_FieldB8;\n        be<float> m_FieldBC;\n        MARATHON_INSERT_PADDING(0x0C);\n        be<float> m_ScaleX;\n        be<float> m_ScaleY;\n        MARATHON_INSERT_PADDING(8);\n        bool m_FieldDC;\n        bool m_FieldDD;\n        MARATHON_INSERT_PADDING(2);\n        xpointer<void> m_FieldE0;                             // Only present when there's character vertices.\n        xpointer<void> m_FieldE4;                             // Only present when there's image vertices.\n        xpointer<SoX::Graphics::Vertex> m_pCharacterVertices; // BL/TL/TR BL/TR/BR (two triangles per character)\n        xpointer<SoX::Graphics::Vertex> m_pImageVertices;     // BL/TL/TR BL/TR/BR (two triangles per image)\n        be<uint32_t> m_CharacterVertexCount;\n        be<uint32_t> m_ImageVertexCount;\n        be<uint32_t> m_TextLength;\n        MARATHON_INSERT_PADDING(4);\n        be<float> m_Field100;\n        be<float> m_Field104;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_spTextFont, 0x1C);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_spTextBoard, 0x24);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_X, 0x30);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Y, 0x34);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Text, 0x40);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Field5C, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_pVariables, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Field80, 0x80);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Width, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldB4, 0xB4);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldB8, 0xB8);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldBC, 0xBC);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_ScaleX, 0xCC);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_ScaleY, 0xD0);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldDC, 0xDC);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldDD, 0xDD);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldE0, 0xE0);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_FieldE4, 0xE4);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_pCharacterVertices, 0xE8);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_pImageVertices, 0xEC);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_CharacterVertexCount, 0xF0);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_ImageVertexCount, 0xF4);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_TextLength, 0xF8);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Field100, 0x100);\n    MARATHON_ASSERT_OFFSETOF(TextEntity, m_Field104, 0x104);\n    MARATHON_ASSERT_SIZEOF(TextEntity, 0x110);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TextFontPicture.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/TextFontPictureMgr.h>\n\nnamespace Sonicteam\n{\n    class TextFontPicture : public SoX::IResource2<TextFontPicture, TextFontPictureMgr>\n    {\n    public:\n        xpointer<uint8_t> m_pResource;\n        xpointer<MyTexture> m_pTexture;\n        be<float> m_TextureWidth;\n        be<float> m_TextureHeight;\n        be<float> m_Field74;\n        xpointer<void> m_Field78;\n        be<uint32_t> m_CropCount;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_pResource, 0x64);\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_pTexture, 0x68);\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_TextureWidth, 0x6C);\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_TextureHeight, 0x70);\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_Field74, 0x74);\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_Field78, 0x78);\n    MARATHON_ASSERT_OFFSETOF(TextFontPicture, m_CropCount, 0x7C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TextFontPictureMgr.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class TextFontPictureMgr : public SoX::IResourceMgr, public System::Singleton<TextFontPictureMgr, 0x82D3C38C, System::CreateStatic<TextFontPictureMgr, 0x8263BEF0>> {};\n\n    MARATHON_ASSERT_SIZEOF(TextFontPictureMgr, 0x10);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/TitleTask.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/SoX/Engine/Task.h>\n\nnamespace Sonicteam\n{\n    class TitleTask : public SoX::Engine::Task\n    {\n    public:\n        static constexpr float ms_DefaultMovieWaitTime = 30.0f;\n\n        enum TitleState : uint32_t\n        {\n            TitleState_Open,\n            TitleState_Wait,\n            TitleState_PressStart,\n            TitleState_OptionsOpen = 5,\n            TitleState_OptionsWait,\n            TitleState_OptionsProceed = 7,\n            TitleState_Proceed,\n            TitleState_Outro = 13\n        };\n\n        be<TitleState> m_State;\n        MARATHON_INSERT_PADDING(0x0C);\n        be<float> m_MovieWaitTime;\n        be<uint32_t> m_Field60;\n        MARATHON_INSERT_PADDING(0x18);\n        be<uint32_t> m_SelectedIndex;\n        MARATHON_INSERT_PADDING(0x20);\n        be<uint32_t> m_CastIndex;\n        MARATHON_INSERT_PADDING(4);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(TitleTask, m_State, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(TitleTask, m_MovieWaitTime, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(TitleTask, m_Field60, 0x60);\n    MARATHON_ASSERT_OFFSETOF(TitleTask, m_SelectedIndex, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(TitleTask, m_CastIndex, 0xA0);\n    MARATHON_ASSERT_SIZEOF(TitleTask, 0xA8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/VehicleMissileCtrl.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyPhantom.h>\n#include <stdx/string.h>\n\nnamespace Sonicteam\n{\n    class MyAnimation;\n    class MyModel;\n    class VehicleLockOnListener;\n\n    namespace Enemy\n    {\n        class ShotInfo;\n        class ShotParameter;\n    }\n\n    namespace SoX::Graphics\n    {\n        class AnimationHierarchyCommon;\n        class FrameGP;\n        class InstanceModelCommon;\n    }\n\n    namespace SoX::Scenery\n    {\n        class Clump;\n    }\n\n    class VehicleMissileCtrl\n    {\n    public:\n        xpointer<void> m_pVftable;\n        MARATHON_INSERT_PADDING(4);\n        be<uint32_t> m_MissileType;\n        MARATHON_INSERT_PADDING(4);\n        stdx::string m_ObjectName;\n        stdx::string m_ShotName;\n        be<float> m_MissileBoxX;\n        be<float> m_MissileBoxY;\n        be<float> m_MissileBoxZ;\n        xpointer<Enemy::ShotInfo> m_pShotInfo;\n        xpointer<Enemy::ShotParameter> m_pShotParameter;\n        MARATHON_INSERT_PADDING(8);\n        be<float> m_Field64;\n        MARATHON_INSERT_PADDING(4);\n        xpointer<MyPhantom> m_pMyPhantom;\n        xpointer<VehicleLockOnListener> m_pVehicleLockOnListener;\n        xpointer<SoX::Graphics::FrameGP> m_apRightGunFrameGPs[2];\n        xpointer<MyModel> m_pRightGunModel;\n        xpointer<SoX::Graphics::InstanceModelCommon> m_pRightGunInstanceModel;\n        xpointer<SoX::Scenery::Clump> m_pRightGunClump;\n        xpointer<MyAnimation> m_pRightGunMotion;\n        xpointer<SoX::Graphics::AnimationHierarchyCommon> m_pRightGunMotionHierarchy;\n        xpointer<SoX::Graphics::FrameGP> m_pLeftGunFrameGP;\n        xpointer<MyModel> m_pLeftGunModel;\n        xpointer<SoX::Graphics::InstanceModelCommon> m_pLeftGunInstanceModel;\n        xpointer<SoX::Scenery::Clump> m_pLeftGunClump;\n        xpointer<MyAnimation> m_pLeftGunMotion;\n        xpointer<SoX::Graphics::AnimationHierarchyCommon> m_pLeftGunMotionHierarchy;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_MissileType, 0x08);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_ObjectName, 0x10);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_ShotName, 0x2C);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_MissileBoxX, 0x48);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_MissileBoxY, 0x4C);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_MissileBoxZ, 0x50);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pShotInfo, 0x54);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pShotParameter, 0x58);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_Field64, 0x64);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pMyPhantom, 0x6C);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pVehicleLockOnListener, 0x70);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_apRightGunFrameGPs, 0x74);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pRightGunModel, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pRightGunInstanceModel, 0x80);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pRightGunClump, 0x84);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pRightGunMotion, 0x88);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pRightGunMotionHierarchy, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pLeftGunFrameGP, 0x90);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pLeftGunModel, 0x94);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pLeftGunInstanceModel, 0x98);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pLeftGunClump, 0x9C);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pLeftGunMotion, 0xA0);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrl, m_pLeftGunMotionHierarchy, 0xA4);\n    MARATHON_ASSERT_SIZEOF(VehicleMissileCtrl, 0xA8);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/VehicleMissileCtrlAutomatic.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/VehicleMissileCtrl.h>\n\nnamespace Sonicteam\n{\n    class VehicleMissileCtrlAutomatic : public VehicleMissileCtrl\n    {\n    public:\n        be<float> m_MissileInterval;\n        be<uint32_t> m_MissileBullet;\n        be<float> m_MissileRecoveryTime;\n        be<float> m_MissileFrame;\n        MARATHON_INSERT_PADDING(0x14);\n        stdx::string m_VehicleName;\n        MARATHON_INSERT_PADDING(8);\n        bool m_IsShot;\n        be<uint32_t> m_FieldF4;\n        MARATHON_INSERT_PADDING(8);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_MissileInterval, 0xA8);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_MissileBullet, 0xAC);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_MissileRecoveryTime, 0xB0);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_MissileFrame, 0xB4);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_VehicleName, 0xCC);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_IsShot, 0xF0);\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlAutomatic, m_FieldF4, 0xF4);\n    MARATHON_ASSERT_SIZEOF(VehicleMissileCtrlAutomatic, 0x100);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/VehicleMissileCtrlSingle.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class VehicleMissileCtrlSingle : public VehicleMissileCtrl\n    {\n    public:\n        be<float> m_MissileRechargeTime;\n        MARATHON_INSERT_PADDING(0x18);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(VehicleMissileCtrlSingle, m_MissileRechargeTime, 0xA8);\n    MARATHON_ASSERT_SIZEOF(VehicleMissileCtrlSingle, 0xC4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/WorldRenderProcess.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <Sonicteam/MyRenderProcess.h>\n\nnamespace Sonicteam\n{\n    class WorldRenderProcess : public MyRenderProcess\n    {\n    public:\n        be<uint32_t> m_PassIndex;\n        MARATHON_INSERT_PADDING(0x4);\n    };\n\n    MARATHON_ASSERT_SIZEOF(WorldRenderProcess, 0x40);\n    MARATHON_ASSERT_OFFSETOF(WorldRenderProcess, m_PassIndex, 0x38);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/Sonicteam/sonicXmaPlayer.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace Sonicteam\n{\n    class sonicXmaPlayer : public SoX::RefCountObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(0xD8);\n        bool m_IsPaused;\n        MARATHON_INSERT_PADDING(0x108);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(sonicXmaPlayer, m_IsPaused, 0xE0);\n    MARATHON_ASSERT_SIZEOF(sonicXmaPlayer, 0x1EC);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/boost/smart_ptr/make_shared_object.h",
    "content": "#pragma once\n\n#include \"boost/smart_ptr/shared_ptr.h\"\n\nnamespace boost\n{\n    template<typename T>\n    shared_ptr<T> make_shared(T* p, uint32_t vftable)\n    {\n        shared_ptr<T> sp;\n\n        auto* counted = (detail::sp_counted_impl_p<T>*)g_userHeap.Alloc(sizeof(detail::sp_counted_impl_p<T>));\n\n        new (counted) detail::sp_counted_impl_p<T>(p);\n\n        sp.px = p;\n        sp.pn = (detail::sp_counted_base*)counted;\n        sp.pn->vftable_.ptr = vftable;\n\n        return sp;\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/api/boost/smart_ptr/shared_ptr.h",
    "content": "#pragma once\n\n#include <cstddef>\n#include <utility>\n#include <Marathon.inl>\n\nnamespace boost\n{\n    namespace detail\n    {\n        class sp_counted_base\n        {\n        protected:\n            struct vftable_t\n            {\n                be<uint32_t> destructor;\n                be<uint32_t> dispose;\n                be<uint32_t> destroy;\n                be<uint32_t> get_deleter;\n            };\n        public:\n            xpointer<vftable_t> vftable_;\n        protected:\n            be<uint32_t> use_count_;\n            be<uint32_t> weak_count_;\n\n        public:\n            // TODO\n            sp_counted_base() {}\n\n            void add_ref()\n            {\n                std::atomic_ref useCount(use_count_.value);\n\n                be<uint32_t> original, incremented;\n                do\n                {\n                    original = use_count_;\n                    incremented = original + 1;\n                } while (!useCount.compare_exchange_weak(original.value, incremented.value));\n            }\n\n            void release()\n            {\n                std::atomic_ref useCount(use_count_.value);\n\n                be<uint32_t> original, decremented;\n                do\n                {\n                    original = use_count_;\n                    decremented = original - 1;\n                } while (!useCount.compare_exchange_weak(original.value, decremented.value));\n\n                if (decremented == 0)\n                {\n                    GuestToHostFunction<void>(vftable_->dispose, this);\n                    weak_release();\n                }\n            }\n\n            void weak_release()\n            {\n                std::atomic_ref weakCount(weak_count_.value);\n\n                be<uint32_t> original, decremented;\n                do\n                {\n                    original = weak_count_;\n                    decremented = original - 1;\n                } while (!weakCount.compare_exchange_weak(original.value, decremented.value));\n\n                if (decremented == 0)\n                {\n                    GuestToHostFunction<void>(vftable_->destroy, this);\n                }\n            }\n\n            uint32_t use_count() const\n            {\n                return ByteSwap(static_cast<uint32_t const volatile&>(use_count_.value));\n            }\n\n            bool unique() const\n            {\n                return use_count() == 1;\n            }\n        };\n\n        template<typename T>\n        class sp_counted_impl_p : public sp_counted_base\n        {\n        public:\n            sp_counted_impl_p(T* p) : ptr_(p)\n            {\n                use_count_ = 1;\n                weak_count_ = 1;\n            }\n\n            ~sp_counted_impl_p() {}\n\n        private:\n            xpointer<T> ptr_;\n        };\n\n        template<class T> struct sp_dereference\n        {\n            typedef T& type;\n        };\n\n        template<> struct sp_dereference<void>\n        {\n            typedef void type;\n        };\n    }\n\n    template<typename T>\n    class shared_ptr\n    {\n    public:\n        xpointer<T> px;\n        xpointer<boost::detail::sp_counted_base> pn;\n\n        void add_ref()\n        {\n            if (pn)\n                pn->add_ref();\n        }\n\n        void release()\n        {\n            if (pn)\n                pn->release();\n        }\n\n        shared_ptr() : px(), pn() {}\n\n        // TODO\n        explicit shared_ptr(T* p) = delete;\n\n        shared_ptr(const shared_ptr& other) : px(other.px), pn(other.pn)\n        {\n            add_ref();\n        }\n\n        shared_ptr(shared_ptr&& other) noexcept : px(std::exchange(other.px, nullptr)),\n            pn(std::exchange(other.pn, nullptr)) {}\n\n        ~shared_ptr()\n        {\n            release();\n        }\n\n        shared_ptr& operator=(const shared_ptr& other)\n        {\n            if (this != &other)\n            {\n                release();\n\n                px = other.px;\n                pn = other.pn;\n\n                add_ref();\n            }\n\n            return *this;\n        }\n\n        shared_ptr& operator=(shared_ptr&& other) noexcept\n        {\n            if (this != &other)\n            {\n                release();\n\n                px = std::exchange(other.px, nullptr);\n                pn = std::exchange(other.pn, nullptr);\n            }\n\n            return *this;\n        }\n\n        shared_ptr& operator=(std::nullptr_t)\n        {\n            release();\n            \n            px = NULL;\n            pn = NULL;\n\n            return *this;\n        }\n\n        T* get() const { return px; }\n\n        detail::sp_dereference<T> operator*() const { assert(px); return *px; }\n        T* operator->() const { assert(px); return px; }\n\n        explicit operator bool() const { return px != nullptr; }\n\n        size_t use_count() const { return pn ? pn->use_count() : 0; }\n        bool unique() const { return !pn || pn->unique(); }\n    };\n\n    using anonymous_shared_ptr = shared_ptr<void>;\n}\n"
  },
  {
    "path": "MarathonRecomp/api/d3dxb.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nenum D3DXBSTENCILOP\n{\n    D3DXBSTENCILOP_KEEP = 0,\n    D3DXBSTENCILOP_ZERO = 1,\n    D3DXBSTENCILOP_REPLACE = 2,\n    D3DXBSTENCILOP_INCRSAT = 3,\n    D3DXBSTENCILOP_DECRSAT = 4,\n    D3DXBSTENCILOP_INVERT = 5,\n    D3DXBSTENCILOP_INCR = 6,\n    D3DXBSTENCILOP_DECR = 7\n};\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkArray.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace hk330\n{\n    template <typename T>\n    class hkArray\n    {\n    public:\n        xpointer<T> m_data;\n        be<uint32_t> m_size;\n        be<uint32_t> m_capacityAndFlags;\n\n        template <typename E>\n        T* GetIndex(E i)\n        {\n            return (T*)(m_data.get() + ((int)i * sizeof(T)));\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkArray<void>, m_data, 0x00);\n    MARATHON_ASSERT_OFFSETOF(hkArray<void>, m_size, 0x04);\n    MARATHON_ASSERT_OFFSETOF(hkArray<void>, m_capacityAndFlags, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkReferencedObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace hk330\n{\n    class hkReferencedObject\n    {\n    public:\n        xpointer<void> m_pVftable;\n        be<uint16_t> m_memSizeAndFlags;\n        be<uint16_t> m_referenceCount;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkReferencedObject, m_pVftable, 0x00);\n    MARATHON_ASSERT_OFFSETOF(hkReferencedObject, m_memSizeAndFlags, 0x04);\n    MARATHON_ASSERT_OFFSETOF(hkReferencedObject, m_referenceCount, 0x06);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpBroadPhaseHandle.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpShape.h>\n\nnamespace hk330\n{\n    class hkpBroadPhaseHandle\n    {\n    public:\n        be<uint32_t> m_id;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpBroadPhaseHandle, m_id, 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpCdBody.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpShape.h>\n\nnamespace hk330\n{\n    class hkpCdBody\n    {\n    public:\n        xpointer<hkpShape> m_shape;\n        be<uint32_t> m_shapeKey;\n        xpointer<void> m_motion;\n        xpointer<hk330::hkpCdBody> m_parent;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpCdBody, m_shape, 0x00);\n    MARATHON_ASSERT_OFFSETOF(hkpCdBody, m_shapeKey, 0x04);\n    MARATHON_ASSERT_OFFSETOF(hkpCdBody, m_motion, 0x08);\n    MARATHON_ASSERT_OFFSETOF(hkpCdBody, m_parent, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpCollidable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpCdBody.h>\n#include <hk330/hkpTypedBroadPhaseHandle.h>\n\nnamespace hk330\n{\n    class hkpCollidable : public hkpCdBody\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        hkpTypedBroadPhaseHandle m_broadPhaseHandle;\n        MARATHON_INSERT_PADDING(0x28);\n        be<float> m_allowedPenetrationDepth;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpCollidable, m_broadPhaseHandle, 0x14);\n    MARATHON_ASSERT_OFFSETOF(hkpCollidable, m_allowedPenetrationDepth, 0x48);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpCollidableCollidableFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace hk330\n{\n    class hkpCollidableCollidableFilter\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpCtor;\n            be<uint32_t> fpIsCollisionEnabled;\n        };\n\n        xpointer<Vftable> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpCollidableCollidableFilter, m_pVftable, 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpCollisionFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkArray.h>\n#include <hk330/hkpCollidableCollidableFilter.h>\n#include <hk330/hkpRayCollidableFilter.h>\n#include <hk330/hkpRayShapeCollectionFilter.h>\n#include <hk330/hkpRigidBody.h>\n#include <hk330/hkpShapeCollectionFilter.h>\n#include <hk330/hkReferencedObject.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n\nnamespace hk330\n{\n    class hkpCollisionFilter : public hkReferencedObject, public hkpCollidableCollidableFilter, public hkpShapeCollectionFilter, public hkpRayShapeCollectionFilter, public hkpRayCollidableFilter\n    {\n    public:\n        be<uint32_t> m_FilterType;\n        MARATHON_INSERT_PADDING(0x0C);\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpCollisionFilter, m_FilterType, 0x18);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpEntity.h",
    "content": "#pragma once\n\n#include <hk330/hkpWorldObject.h>\n\nnamespace hk330\n{\n    class hkpEntity : public hkpWorldObject\n    {\n    public:\n        MARATHON_INSERT_PADDING(0x1E0);\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpLinkedCollidable.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkArray.h>\n#include <hk330/hkpCollidable.h>\n\nnamespace hk330\n{\n    class hkpLinkedCollidable : public hkpCollidable\n    {\n    public:\n        struct CollisionEntry\n        {\n            MARATHON_INSERT_PADDING(4);\n            xpointer<hkpLinkedCollidable> m_partner;\n        };\n\n        hkArray<CollisionEntry> m_collisionEntries;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpLinkedCollidable::CollisionEntry, m_partner, 0x04);\n\n    MARATHON_ASSERT_OFFSETOF(hkpLinkedCollidable, m_collisionEntries, 0x4C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpPhantom.h",
    "content": "#pragma once\n\n#include <hk330/hkArray.h>\n#include <hk330/hkpWorldObject.h>\n\nnamespace hk330\n{\n    class hkpPhantom : public hkpWorldObject\n    {\n    public:\n        hkArray<xpointer<void>> m_overlapListeners;\n        hkArray<xpointer<void>> m_phantomListeners;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpPhantom, m_overlapListeners, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(hkpPhantom, m_phantomListeners, 0x98);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpProperty.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpShape.h>\n\nnamespace hk330\n{\n    class hkpProperty\n    {\n    public:\n        MARATHON_INSERT_PADDING(8);\n    };\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpRayCollidableFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace hk330\n{\n    class hkpRayCollidableFilter\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpCtor;\n            be<uint32_t> fpIsCollisionEnabled;\n        };\n\n        xpointer<Vftable> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpRayCollidableFilter, m_pVftable, 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpRayShapeCollectionFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace hk330\n{\n    class hkpRayShapeCollectionFilter\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpCtor;\n            be<uint32_t> fpIsCollisionEnabled;\n        };\n\n        xpointer<Vftable> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpRayShapeCollectionFilter, m_pVftable, 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpRigidBody.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpEntity.h>\n\nnamespace hk330\n{\n    class hkpRigidBody : public hkpEntity {};\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpShape.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkReferencedObject.h>\n\nnamespace hk330\n{\n    class hkpShape : public hkReferencedObject\n    {\n    public:\n        be<uint32_t> m_userData;\n        be<uint32_t> m_type;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpShape, m_userData, 0x08);\n    MARATHON_ASSERT_OFFSETOF(hkpShape, m_type, 0x0C);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpShapeCollectionFilter.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n\nnamespace hk330\n{\n    class hkpShapeCollectionFilter\n    {\n    public:\n        struct Vftable\n        {\n            be<uint32_t> fpCtor;\n            be<uint32_t> fpIsCollisionEnabled;\n        };\n\n        xpointer<Vftable> m_pVftable;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpShapeCollectionFilter, m_pVftable, 0x00);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpTypedBroadPhaseHandle.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkpBroadPhaseHandle.h>\n\nnamespace hk330\n{\n    class hkpTypedBroadPhaseHandle : public hkpBroadPhaseHandle\n    {\n    public: \n        int8_t m_type;\n        int8_t m_ownerOffset;\n        be<int16_t> m_objectQualityType;\n        be<uint32_t> m_collisionFilterInfo;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpTypedBroadPhaseHandle, m_type, 0x04);\n    MARATHON_ASSERT_OFFSETOF(hkpTypedBroadPhaseHandle, m_ownerOffset, 0x05);\n    MARATHON_ASSERT_OFFSETOF(hkpTypedBroadPhaseHandle, m_objectQualityType, 0x06);\n    MARATHON_ASSERT_OFFSETOF(hkpTypedBroadPhaseHandle, m_collisionFilterInfo, 0x08);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpWorld.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkArray.h>\n#include <hk330/hkReferencedObject.h>\n#include <Sonicteam/SoX/Math/Vector.h>\n\nnamespace hk330\n{\n    class hkpBroadPhase;\n    class hkpBroadPhaseBorderListener;\n    class hkpCollisionDispatcher;\n    class hkpCollisionFilter;\n    class hkpEntityEntityBroadPhaseListener;\n    class hkpPhantom;\n    class hkpPhantomBroadPhaseListener;\n    class hkpProcessCollisionInput;\n    class hkpRigidBody;\n    class hkpSimulation;\n    class hkpSimulationIsland;\n    class hkpTypedBroadPhaseDispatcher;\n    class hkpWorldMaintenanceMgr;\n    class hkpWorldOperationQueue;\n    class hkWorldMemoryAvailableWatchDog;\n\n    class hkpWorld : public hkReferencedObject\n    {\n    public:\n        xpointer<hkpSimulation> m_simulation;\n        MARATHON_INSERT_PADDING(0x14);\n        Sonicteam::SoX::Math::Vector m_gravity;\n        xpointer<hkpSimulationIsland> m_fixedIsland;\n        xpointer<hkpRigidBody> m_fixedRigidBody;\n        hkArray<xpointer<hkpSimulationIsland>> m_activeSimulationIslands;\n        hkArray<xpointer<hkpSimulationIsland>> m_inactiveSimulationIslands;\n        hkArray<xpointer<hkpSimulationIsland>> m_dirtySimulationIslands;\n        xpointer<hkpWorldMaintenanceMgr> m_maintenanceMgr;\n        xpointer<hkWorldMemoryAvailableWatchDog> m_memoryWatchDog;\n        xpointer<hkpBroadPhase> m_broadPhase;\n        xpointer<hkpTypedBroadPhaseDispatcher> m_broadPhaseDispatcher;\n        xpointer<hkpPhantomBroadPhaseListener> m_phantomBroadPhaseListener;\n        xpointer<hkpEntityEntityBroadPhaseListener> m_entityEntityBroadPhaseListener;\n        xpointer<hkpBroadPhaseBorderListener> m_broadPhaseBorderListener;\n        xpointer<hkpProcessCollisionInput> m_collisionInput;\n        xpointer<hkpCollisionFilter> m_collisionFilter;\n        xpointer<hkpCollisionDispatcher> m_collisionDispatcher;\n        xpointer<hkpWorldOperationQueue> m_pendingOperations;\n        be<int32_t> m_pendingOperationsCount;\n        be<int32_t> m_criticalOperationsLockCount;\n        be<int32_t> m_criticalOperationsLockCountForPhantoms;\n        bool m_blockExecutingPendingOperations;\n        bool m_criticalOperationsAllowed;\n        MARATHON_INSERT_PADDING(0x2C);\n        hkArray<xpointer<hkpPhantom>> m_phantoms;\n\n        void updateCollisionFilterOnWorld(uint32_t updateMode, uint32_t updateShapeCollectionFilter)\n        {\n            GuestToHostFunction<void>(sub_82832910, this, updateMode, updateShapeCollectionFilter);\n        }\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_simulation, 0x08);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_gravity, 0x20);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_fixedIsland, 0x30);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_fixedRigidBody, 0x34);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_activeSimulationIslands, 0x38);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_inactiveSimulationIslands, 0x44);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_dirtySimulationIslands, 0x50);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_maintenanceMgr, 0x5C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_memoryWatchDog, 0x60);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_broadPhase, 0x64);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_broadPhaseDispatcher, 0x68);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_phantomBroadPhaseListener, 0x6C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_entityEntityBroadPhaseListener, 0x70);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_broadPhaseBorderListener, 0x74);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_collisionInput, 0x78);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_collisionFilter, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_collisionDispatcher, 0x80);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_pendingOperations, 0x84);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_pendingOperationsCount, 0x88);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_criticalOperationsLockCount, 0x8C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_criticalOperationsLockCountForPhantoms, 0x90);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_blockExecutingPendingOperations, 0x94);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_criticalOperationsAllowed, 0x95);\n    MARATHON_ASSERT_OFFSETOF(hkpWorld, m_phantoms, 0xC4);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/hk330/hkpWorldObject.h",
    "content": "#pragma once\n\n#include <Marathon.inl>\n#include <hk330/hkArray.h>\n#include <hk330/hkpProperty.h>\n#include <hk330/hkpWorld.h>\n#include <hk330/hkReferencedObject.h>\n#include <hk330/hkpLinkedCollidable.h>\n\nnamespace hk330\n{\n    class hkpWorldObject : public hkReferencedObject\n    {\n    public:\n        xpointer<hkpWorld> m_world;\n        be<uint32_t> m_userData;\n        MARATHON_INSERT_PADDING(0x0C);\n        hkpLinkedCollidable m_collidable;\n        MARATHON_INSERT_PADDING(8);\n        xpointer<const char> m_name;\n        hkArray<hkpProperty> m_properties;\n    };\n\n    MARATHON_ASSERT_OFFSETOF(hkpWorldObject, m_world, 0x08);\n    MARATHON_ASSERT_OFFSETOF(hkpWorldObject, m_userData, 0x0C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorldObject, m_collidable, 0x1C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorldObject, m_name, 0x7C);\n    MARATHON_ASSERT_OFFSETOF(hkpWorldObject, m_properties, 0x80);\n}\n"
  },
  {
    "path": "MarathonRecomp/api/stdx/string.h",
    "content": "#pragma once\n\n#include <kernel/heap.h>\n#include <kernel/function.h>\n\nnamespace stdx\n{\n    class string\n    {\n    private:\n        union _Bxty\n        {\n            _Bxty() {}\n\n            char _buffer[0x10];\n            xpointer<const char> _str;\n        };\n\n        be<uint32_t> _Myproxy;\n        _Bxty _bx;\n        be<uint32_t> _Mysize;\n        be<uint32_t> _Myres;\n\n        bool is_short() const\n        {\n            return _Mysize <= 0xF;\n        }\n\n    public:\n        const char* c_str() const\n        {\n            return is_short() ? (const char*)&_bx._buffer : (const char*)_bx._str.get();\n        }\n\n        size_t size() const\n        {\n            return _Mysize.get();\n        }\n\n        size_t capacity() const\n        {\n            return _Myres.get();\n        }\n\n        string()\n        {\n            _Myres = 0xF;\n            _Mysize = 0;\n            _bx._buffer[0] = '\\0';\n        }\n\n        string(xpointer<const char> str) : string(str.get()) {}\n\n        string(const char* str)\n        {\n            _Myres = 0xF;\n            _Mysize = 0;\n            _bx._buffer[0] = '\\0';\n            from_cstr(str);\n        }\n\n        ~string()\n        {\n            cleanup();\n            _Myres = 0xF;\n            _Mysize = 0;\n            _bx._buffer[0] = '\\0';\n        }\n\n        void cleanup()\n        {\n            if (!is_short())\n                g_userHeap.Free((void*)_bx._str.get());\n        }\n\n        void from_cstr(const char* str)\n        {\n            auto len = strlen(str);\n\n            if (len <= 0xF)\n            {\n                memcpy((void*)&_bx._buffer, str, len + 1);\n                _Mysize = (uint32_t)(len);\n            }\n            else\n            {\n                if (is_short() || capacity() < len + 1)\n                {\n                    cleanup();\n                    char* new_buf = g_userHeap.Alloc<char>(len + 1);\n                    memset((void*)(new_buf), 0, len + 1);\n                    memcpy((void*)(new_buf), (const void*)(str), len + 1);\n                    _bx._str = new_buf;\n                    _Myres = len + 1;\n                }\n                else\n                {\n                    memcpy((void*)_bx._str.get(), (void*)str, len + 1);\n                }\n\n                _Mysize = len;\n            }\n        }\n\n        string& operator=(const char* str)\n        {\n            from_cstr(str);\n            return *this;\n        }\n\n        bool operator==(const char* str) const\n        {\n            return strcmp(c_str(), str) == 0;\n        }\n    };\n};\n"
  },
  {
    "path": "MarathonRecomp/api/stdx/vector.h",
    "content": "#pragma once\n\n#include <kernel/heap.h>\n#include <kernel/function.h>\n#include <utility>\n#include <algorithm>\n\nnamespace stdx\n{\n    template <typename T>\n    class vector\n    {\n    private:\n        be<uint32_t> _Myproxy;\n        xpointer<T> _MyFirst;\n        xpointer<T> _MyLast;\n        xpointer<T> _MyEnd;\n\n        static xpointer<T> xpointer_add(xpointer<T> ptr, size_t offset)\n        {\n            return xpointer<T>(reinterpret_cast<T*>(\n                reinterpret_cast<uintptr_t>(ptr.get()) + offset * sizeof(T)));\n        }\n\n        void _ConstructRange(T* first, T* last, const T& value)\n        {\n            for (; first != last; ++first)\n                new (first) T(value);\n        }\n\n        void _DestroyRange(T* first, T* last)\n        {\n            while (first != last)\n            {\n                first->~T();\n                ++first;\n            }\n        }\n\n        void _Reallocate(size_t new_capacity)\n        {\n            T* new_block = static_cast<T*>(g_userHeap.Alloc(sizeof(T) * new_capacity));\n            const size_t old_size = size();\n\n            for (size_t i = 0; i < old_size; ++i)\n            {\n                T* target = reinterpret_cast<T*>(\n                    reinterpret_cast<uintptr_t>(new_block) + i * sizeof(T));\n                new (target) T(std::move(_MyFirst[i]));\n                _MyFirst[i].~T();\n            }\n\n            if (_MyFirst)\n            {\n                _DestroyRange(_MyFirst.get(), _MyLast.get());\n                g_userHeap.Free(_MyFirst.get());\n            }\n\n            _MyFirst = xpointer<T>(new_block);\n            _MyLast = xpointer_add(_MyFirst, old_size);\n            _MyEnd = xpointer_add(_MyFirst, new_capacity);\n        }\n\n        void _GrowIfNeeded()\n        {\n            if (_MyLast.get() == _MyEnd.get())\n                _Reallocate(size() ? size() * 2 : 1);\n        }\n\n        class iterator_wrapper\n        {\n        private:\n            xpointer<T> _ptr;\n\n        public:\n            explicit iterator_wrapper(xpointer<T> ptr) : _ptr(ptr) {}\n\n            T& operator*() const\n            {\n                return *_ptr;\n            }\n\n            T* operator->() const\n            {\n                return _ptr.get();\n            }\n\n            iterator_wrapper& operator++()\n            {\n                _ptr = xpointer<T>(reinterpret_cast<T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) + sizeof(T)));\n\n                return *this;\n            }\n\n            iterator_wrapper operator++(int)\n            {\n                iterator_wrapper tmp = *this;\n                ++(*this);\n                return tmp;\n            }\n\n            iterator_wrapper& operator--()\n            {\n                _ptr = xpointer<T>(reinterpret_cast<T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) - sizeof(T)));\n\n                return *this;\n            }\n\n            iterator_wrapper operator--(int)\n            {\n                iterator_wrapper tmp = *this;\n                --(*this);\n                return tmp;\n            }\n\n            iterator_wrapper operator+(size_t n) const\n            {\n                return iterator_wrapper(xpointer<T>(reinterpret_cast<T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) + n * sizeof(T))));\n            }\n\n            iterator_wrapper operator-(size_t n) const\n            {\n                return iterator_wrapper(xpointer<T>(reinterpret_cast<T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) - n * sizeof(T))));\n            }\n\n            bool operator==(const iterator_wrapper& other) const\n            {\n                return _ptr.get() == other._ptr.get();\n            }\n\n            bool operator!=(const iterator_wrapper& other) const\n            {\n                return _ptr.get() != other._ptr.get();\n            }\n        };\n\n        class const_iterator_wrapper\n        {\n        private:\n            xpointer<const T> _ptr;\n\n        public:\n            explicit const_iterator_wrapper(xpointer<const T> ptr) : _ptr(ptr) {}\n\n            const T& operator*() const\n            {\n                return *_ptr;\n            }\n\n            const T* operator->() const\n            {\n                return _ptr.get();\n            }\n\n            const_iterator_wrapper& operator++()\n            {\n                _ptr = xpointer<const T>(reinterpret_cast<const T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) + sizeof(T)));\n\n                return *this;\n            }\n\n            const_iterator_wrapper operator++(int)\n            {\n                const_iterator_wrapper tmp = *this;\n                ++(*this);\n                return tmp;\n            }\n\n            const_iterator_wrapper& operator--()\n            {\n                _ptr = xpointer<const T>(reinterpret_cast<const T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) - sizeof(T)));\n\n                return *this;\n            }\n\n            const_iterator_wrapper operator--(int)\n            {\n                const_iterator_wrapper tmp = *this;\n                --(*this);\n                return tmp;\n            }\n\n            const_iterator_wrapper operator+(size_t n) const\n            {\n                return const_iterator_wrapper(xpointer<const T>(reinterpret_cast<const T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) + n * sizeof(T))));\n            }\n\n            const_iterator_wrapper operator-(size_t n) const\n            {\n                return const_iterator_wrapper(xpointer<const T>(reinterpret_cast<const T*>(\n                    reinterpret_cast<uintptr_t>(_ptr.get()) - n * sizeof(T))));\n            }\n\n            bool operator==(const const_iterator_wrapper& other) const\n            {\n                return _ptr.get() == other._ptr.get();\n            }\n\n            bool operator!=(const const_iterator_wrapper& other) const\n            {\n                return _ptr.get() != other._ptr.get();\n            }\n        };\n\n    public:\n        using iterator = iterator_wrapper;\n        using const_iterator = const_iterator_wrapper;\n\n        vector() noexcept : _MyFirst(nullptr), _MyLast(nullptr), _MyEnd(nullptr) {}\n\n        explicit vector(size_t count)\n        {\n            _MyFirst = xpointer<T>(static_cast<T*>(g_userHeap.Alloc(sizeof(T) * count)));\n            _MyLast = xpointer_add(_MyFirst, count);\n            _MyEnd = _MyLast;\n            _ConstructRange(_MyFirst.get(), _MyLast.get(), T());\n        }\n\n        vector(size_t count, const T& value)\n        {\n            _MyFirst = xpointer<T>(static_cast<T*>(g_userHeap.Alloc(sizeof(T) * count)));\n            _MyLast = xpointer_add(_MyFirst, count);\n            _MyEnd = _MyLast;\n            _ConstructRange(_MyFirst.get(), _MyLast.get(), value);\n        }\n\n        vector(const vector& other)\n        {\n            _MyFirst = xpointer<T>(static_cast<T*>(g_userHeap.Alloc(sizeof(T) * other.size())));\n            _MyLast = xpointer_add(_MyFirst, other.size());\n            _MyEnd = _MyLast;\n\n            for (size_t i = 0; i < other.size(); ++i)\n                new (xpointer_add(_MyFirst, i).get()) T(other._MyFirst[i]);\n        }\n\n        vector(vector&& other) noexcept : _MyFirst(other._MyFirst), _MyLast(other._MyLast), _MyEnd(other._MyEnd)\n        {\n            other._MyFirst = other._MyLast = other._MyEnd = nullptr;\n        }\n\n        ~vector()\n        {\n            clear();\n\n            if (_MyFirst)\n                g_userHeap.Free(_MyFirst.get());\n        }\n\n        vector& operator=(const vector& other)\n        {\n            if (this != &other)\n            {\n                vector tmp(other);\n                swap(tmp);\n            }\n\n            return *this;\n        }\n\n        vector& operator=(vector&& other) noexcept\n        {\n            if (this != &other)\n            {\n                clear();\n\n                if (_MyFirst)\n                    g_userHeap.Free(_MyFirst.get());\n\n                _MyFirst = other._MyFirst;\n                _MyLast = other._MyLast;\n                _MyEnd = other._MyEnd;\n\n                other._MyFirst = other._MyLast = other._MyEnd = nullptr;\n            }\n\n            return *this;\n        }\n\n        T& operator[](size_t pos)\n        {\n            return _MyFirst.get()[pos];\n        }\n\n        const T& operator[](size_t pos) const\n        {\n            return _MyFirst.get()[pos];\n        }\n\n        T& at(size_t pos)\n        {\n            return _MyFirst.get()[pos];\n        }\n\n        const T& at(size_t pos) const\n        {\n            return _MyFirst.get()[pos];\n        }\n\n        T& front()\n        {\n            return *_MyFirst;\n        }\n\n        const T& front() const\n        {\n            return *_MyFirst;\n        }\n\n        T& back()\n        {\n            return *(xpointer_add(_MyLast, -1).get());\n        }\n\n        const T& back() const\n        {\n            return *(xpointer_add(_MyLast, -1).get());\n        }\n\n        T* data()\n        {\n            return _MyFirst.get();\n        }\n\n        const T* data() const\n        {\n            return _MyFirst.get();\n        }\n\n        iterator begin()\n        {\n            return iterator(_MyFirst);\n        }\n\n        const_iterator begin() const\n        {\n            return const_iterator(_MyFirst);\n        }\n\n        const_iterator cbegin() const\n        {\n            return const_iterator(_MyFirst);\n        }\n\n        iterator end()\n        {\n            return iterator(_MyLast);\n        }\n\n        const_iterator end() const\n        {\n            return const_iterator(_MyLast);\n        }\n\n        const_iterator cend() const\n        {\n            return const_iterator(_MyLast);\n        }\n\n        bool empty() const\n        {\n            return _MyFirst.get() == _MyLast.get();\n        }\n\n        size_t size() const\n        {\n            return (_MyLast.get() - _MyFirst.get());\n        }\n\n        size_t capacity() const\n        {\n            return (_MyEnd.get() - _MyFirst.get());\n        }\n\n        void reserve(size_t new_capacity)\n        {\n            if (new_capacity > capacity())\n                _Reallocate(new_capacity);\n        }\n\n        void shrink_to_fit()\n        {\n            if (size() < capacity())\n                _Reallocate(size());\n        }\n\n        void clear()\n        {\n            _DestroyRange(_MyFirst.get(), _MyLast.get());\n            _MyLast = _MyFirst;\n        }\n\n        void push_back(const T& value)\n        {\n            _GrowIfNeeded();\n            new (_MyLast.get()) T(value);\n            _MyLast = xpointer_add(_MyLast, 1);\n        }\n\n        void push_back(T&& value)\n        {\n            _GrowIfNeeded();\n            new (_MyLast.get()) T(std::move(value));\n            _MyLast = xpointer_add(_MyLast, 1);\n        }\n\n        template <typename... Args>\n        void emplace_back(Args&&... args)\n        {\n            _GrowIfNeeded();\n            new (_MyLast.get()) T(std::forward<Args>(args)...);\n            _MyLast = xpointer_add(_MyLast, 1);\n        }\n\n        void pop_back()\n        {\n            _MyLast = xpointer_add(_MyLast, -1);\n            _MyLast.get()->~T();\n        }\n\n        void resize(size_t count)\n        {\n            if (count < size())\n            {\n                _DestroyRange(xpointer_add(_MyFirst, count).get(), _MyLast.get());\n                _MyLast = xpointer_add(_MyFirst, count);\n            }\n            else if (count > size())\n            {\n                reserve(count);\n\n                while (_MyLast.get() != xpointer_add(_MyFirst, count).get())\n                {\n                    new (_MyLast.get()) T();\n                    _MyLast = xpointer_add(_MyLast, 1);\n                }\n            }\n        }\n\n        void resize(size_t count, const T& value)\n        {\n            if (count < size())\n            {\n                _DestroyRange(xpointer_add(_MyFirst, count).get(), _MyLast.get());\n                _MyLast = xpointer_add(_MyFirst, count);\n            }\n            else if (count > size())\n            {\n                reserve(count);\n\n                while (_MyLast.get() != xpointer_add(_MyFirst, count).get())\n                {\n                    new (_MyLast.get()) T(value);\n                    _MyLast = xpointer_add(_MyLast, 1);\n                }\n            }\n        }\n\n        void swap(vector& other) noexcept\n        {\n            std::swap(_MyFirst, other._MyFirst);\n            std::swap(_MyLast, other._MyLast);\n            std::swap(_MyEnd, other._MyEnd);\n        }\n    };\n\n    template <typename T>\n    void swap(vector<T>& lhs, vector<T>& rhs) noexcept\n    {\n        lhs.swap(rhs);\n    }\n\n    template <typename T>\n    bool operator==(const vector<T>& lhs, const vector<T>& rhs)\n    {\n        return lhs.size() == rhs.size()\n            && std::equal(lhs.begin(), lhs.end(), rhs.begin());\n    }\n\n    template <typename T>\n    bool operator!=(const vector<T>& lhs, const vector<T>& rhs)\n    {\n        return !(lhs == rhs);\n    }\n\n    template <typename T>\n    bool operator<(const vector<T>& lhs, const vector<T>& rhs)\n    {\n        return std::lexicographical_compare(\n            lhs.begin(), lhs.end(), rhs.begin(), rhs.end());\n    }\n\n    template <typename T>\n    bool operator<=(const vector<T>& lhs, const vector<T>& rhs)\n    {\n        return !(rhs < lhs);\n    }\n\n    template <typename T>\n    bool operator>(const vector<T>& lhs, const vector<T>& rhs)\n    {\n        return rhs < lhs;\n    }\n\n    template <typename T>\n    bool operator>=(const vector<T>& lhs, const vector<T>& rhs)\n    {\n        return !(lhs < rhs);\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/api/stdx/wstring.h",
    "content": "#pragma once\n\n#include <kernel/heap.h>\n#include <kernel/function.h>\n\nnamespace stdx\n{\n    class wstring\n    {\n    private:\n        union _Bxty\n        {\n            _Bxty() {}\n\n            uint16_t _buffer[8];\n            xpointer<const uint16_t> _str;\n        };\n\n        be<uint32_t> _Myproxy;\n        _Bxty _bx;\n        be<uint32_t> _Mysize;\n        be<uint32_t> _Myres;\n\n        bool is_short() const\n        {\n            return _Mysize < 8;\n        }\n\n    public:\n        const uint16_t* c_str() const\n        {\n            return is_short() ? (const uint16_t*)&_bx._buffer : (const uint16_t*)_bx._str.get();\n        }\n\n        size_t size() const\n        {\n            return _Mysize.get();\n        }\n\n        size_t capacity() const\n        {\n            return _Myres.get();\n        }\n\n        wstring()\n        {\n            _Myres = 0xF;\n            _Mysize = 0;\n            _bx._buffer[0] = '\\0';\n            _bx._buffer[1] = '\\0';\n        }\n\n        wstring(xpointer<const uint16_t> str) : wstring(str.get()) {}\n\n        wstring(const uint16_t* str)\n        {\n            _Myres = 0xF;\n            _Mysize = 0;\n            _bx._buffer[0] = '\\0';\n            _bx._buffer[1] = '\\0';\n\n            auto len = strlenU16(str);\n\n            if (len <= 0xF)\n            {\n                memcpy((void*)&_bx._buffer, str, len + 2);\n                _Mysize = (uint32_t)(len);\n            }\n            else\n            {\n                if (is_short() || capacity() < len + 2)\n                {\n                    auto new_buf = g_userHeap.Alloc<uint16_t>(len + 2);\n                    memset((void*)(new_buf), 0, len + 2);\n                    memcpy((void*)(new_buf), (const void*)(str), len + 2);\n                    _bx._str = new_buf;\n                    _Myres = len + 2;\n                }\n                else\n                {\n                    memcpy((void*)_bx._str.get(), (void*)str, len + 2);\n                }\n\n                _Mysize = len;\n            }\n        }\n\n        ~wstring()\n        {\n            if (!is_short())\n                g_userHeap.Free((void*)_bx._str.get());\n\n            _Myres = 0xF;\n            _Mysize = 0;\n            _bx._buffer[0] = '\\0';\n            _bx._buffer[1] = '\\0';\n        }\n\n        bool operator==(const uint16_t* str) const\n        {\n            return strcmpU16(c_str(), str, false, true);\n        }\n\n        bool operator==(xpointer<const uint16_t> str) const\n        {\n            return strcmpU16(c_str(), str);\n        }\n    };\n};\n"
  },
  {
    "path": "MarathonRecomp/app.cpp",
    "content": "#include \"app.h\"\n#include <gpu/video.h>\n#include <install/installer.h>\n#include <kernel/function.h>\n#include <os/process.h>\n#include <os/logger.h>\n#include <patches/audio_patches.h>\n#include <patches/patches.h>\n#include <ui/game_window.h>\n#include <user/config.h>\n#include <user/paths.h>\n#include <user/registry.h>\n\nstatic std::thread::id g_mainThreadId = std::this_thread::get_id();\n\nvoid App::Restart(std::vector<std::string> restartArgs)\n{\n    os::process::StartProcess(os::process::GetExecutablePath(), restartArgs, os::process::GetWorkingDirectory());\n    Exit();\n}\n\nvoid App::Exit()\n{\n    Config::Save();\n\n#ifdef _WIN32\n    timeEndPeriod(1);\n#endif\n\n    std::_Exit(0);\n}\n\n// Sonicteam::AppMarathon::AppMarathon\nPPC_FUNC_IMPL(__imp__sub_8262A568);\nPPC_FUNC(sub_8262A568)\n{\n    App::s_isInit = true;\n    App::s_isMissingDLC = true;\n    App::s_language = Config::Language;\n\n    Sonicteam::Globals::Init();\n    Registry::Save();\n\n    struct RenderConfig\n    {\n        be<uint32_t> Width;\n        be<uint32_t> Height;\n    };\n\n    auto pRenderConfig = reinterpret_cast<RenderConfig*>(g_memory.Translate(ctx.r4.u32));\n    pRenderConfig->Width = Video::s_viewportWidth;\n    pRenderConfig->Height = Video::s_viewportHeight;\n\n    auto pAudioEngine = Sonicteam::AudioEngineXenon::GetInstance();\n    pAudioEngine->m_MusicVolume = Config::MusicVolume * Config::MasterVolume;\n    pAudioEngine->m_EffectsVolume = Config::EffectsVolume * Config::MasterVolume;\n\n    LOGFN_UTILITY(\"Changed resolution: {}x{}\", pRenderConfig->Width.get(), pRenderConfig->Height.get());\n\n    __imp__sub_8262A568(ctx, base);\n\n    App::s_pApp = (Sonicteam::AppMarathon*)g_memory.Translate(ctx.r3.u32);\n\n    InitPatches();\n}\n\n// Sonicteam::DocMarathonState::Update\nPPC_FUNC_IMPL(__imp__sub_825EA610);\nPPC_FUNC(sub_825EA610)\n{\n    Video::WaitOnSwapChain();\n\n    // Correct small delta time errors.\n    if (Config::FPS >= FPS_MIN && Config::FPS < FPS_MAX)\n    {\n        double targetDeltaTime = 1.0 / Config::FPS;\n\n        if (abs(ctx.f1.f64 - targetDeltaTime) < 0.00001)\n            ctx.f1.f64 = targetDeltaTime;\n    }\n\n    App::s_deltaTime = ctx.f1.f64;\n    App::s_time += App::s_deltaTime;\n\n    // This function can also be called by the loading thread,\n    // which SDL does not like. To prevent the OS from thinking\n    // the process is unresponsive, we will flush while waiting\n    // for the pipelines to finish compiling in video.cpp.\n    if (std::this_thread::get_id() == g_mainThreadId)\n    {\n        SDL_PumpEvents();\n        SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);\n        GameWindow::Update();\n    }\n\n    // Allow variable FPS when config is not 60 FPS.\n    App::s_pApp->m_pDoc->m_VFrame = Config::FPS != 60;\n\n    AudioPatches::Update(App::s_deltaTime);\n\n    __imp__sub_825EA610(ctx, base);\n}\n\nPPC_FUNC_IMPL(__imp__sub_82582648);\nPPC_FUNC(sub_82582648)\n{\n    struct File\n    {\n    public:\n        MARATHON_INSERT_PADDING(4);\n        xpointer<const char> pFilePath;\n        MARATHON_INSERT_PADDING(0x0C);\n        be<uint32_t> Length;\n        be<uint32_t> Capacity;\n    };\n\n    auto pFile = reinterpret_cast<File*>(base + ctx.r5.u32);\n\n    if (pFile->pFilePath && pFile->Length > 0)\n        LOGFN_UTILITY(\"Loading file: {}\", pFile->pFilePath.get());\n\n    __imp__sub_82582648(ctx, base);\n}\n\n#if _DEBUG\n// Sonicteam::SoX::Thread::Thread\nPPC_FUNC_IMPL(__imp__sub_825867A8);\nPPC_FUNC(sub_825867A8)\n{\n    auto pThreadName = (const char*)g_memory.Translate(ctx.r4.u32);\n\n    os::logger::Log(fmt::format(\"Created thread: {}\", pThreadName), os::logger::ELogType::Utility, \"Sonicteam::SoX::Thread\");\n\n    __imp__sub_825867A8(ctx, base);\n}\n#endif\n\nPPC_FUNC_IMPL(__imp__sub_82744840);\nPPC_FUNC(sub_82744840)\n{\n    LOG_UTILITY(\"RenderFrame\");\n\n    __imp__sub_82744840(ctx, base);\n}\n\n// Sonicteam::SpanverseHeap::Alloc\nPPC_FUNC_IMPL(__imp__sub_825E7918);\nPPC_FUNC(sub_825E7918)\n{\n#if _DEBUG\n    os::logger::Log(fmt::format(\"Allocated {} bytes\", ctx.r3.u32), os::logger::ELogType::Utility, \"Sonicteam::SpanverseHeap\");\n#endif\n\n    // This function checks if R4 is non-zero\n    // to allow an allocation, but it's always\n    // passed in as zero.\n    ctx.r4.u32 = 1;\n\n    __imp__sub_825E7918(ctx, base);\n}\n\n// Sonicteam::SpanverseHeap::Free\nPPC_FUNC_IMPL(__imp__sub_825E7958);\nPPC_FUNC(sub_825E7958)\n{\n#if _DEBUG\n    os::logger::Log(fmt::format(\"Freed {:08X}\", ctx.r3.u32), os::logger::ELogType::Utility, \"Sonicteam::SpanverseHeap\");\n#endif\n\n    // This function checks if R4 is non-zero\n    // to allow a free, but it's always\n    // passed in as zero.\n    ctx.r4.u32 = 1;\n\n    __imp__sub_825E7958(ctx, base);\n}\n\n#if _DEBUG\nPPC_FUNC_IMPL(__imp__sub_825822D0);\nPPC_FUNC(sub_825822D0)\n{\n    LOG_UTILITY(\"!!!\");\n\n    __imp__sub_825822D0(ctx, base);\n}\n#endif\n"
  },
  {
    "path": "MarathonRecomp/app.h",
    "content": "#pragma once\n\n#include <api/Marathon.h>\n#include <user/config.h>\n\nclass App\n{\npublic:\n    static inline bool s_isInit;\n    static inline bool s_isSkipLogos;\n    static inline bool s_isMissingDLC;\n    static inline bool s_isLoading;\n    static inline bool s_isSaving;\n    static inline bool s_isSaveDataCorrupt;\n\n    static inline Sonicteam::AppMarathon* s_pApp;\n\n    static inline EPlayerCharacter s_playerCharacter;\n    static inline ELanguage s_language;\n\n    static inline double s_deltaTime;\n    static inline double s_time = 0.0; // How much time elapsed since the game started.\n\n    static void Restart(std::vector<std::string> restartArgs = {});\n    static void Exit();\n};\n\n"
  },
  {
    "path": "MarathonRecomp/apu/audio.cpp",
    "content": "#include <stdafx.h>\n\n#include <bit>\n\n#include \"audio.h\"\n#include <kernel/memory.h>\n\n#define AUDIO_DRIVER_KEY (uint32_t)('DAUD')\n\n// Use to dump raw audio captures to the game folder.\n//#define AUDIO_DUMP_SAMPLES_PATH \"audio.pcm\"\n\n#ifdef AUDIO_DUMP_SAMPLES_PATH\nstd::ofstream g_audioDumpStream;\n#endif\n\nuint32_t XAudioRegisterRenderDriverClient(be<uint32_t>* callback, be<uint32_t>* driver)\n{\n#ifdef AUDIO_DUMP_SAMPLES_PATH\n    g_audioDumpStream.open(AUDIO_DUMP_SAMPLES_PATH, std::ios::binary);\n#endif\n\n    *driver = AUDIO_DRIVER_KEY;\n    XAudioRegisterClient(g_memory.FindFunction(*callback), callback[1]);\n    return 0;\n}\n\nuint32_t XAudioUnregisterRenderDriverClient(uint32_t driver)\n{\n    return 0;\n}\n\nuint32_t XAudioSubmitRenderDriverFrame(uint32_t driver, void* samples)\n{\n#ifdef AUDIO_DUMP_SAMPLES_PATH\n    static uint32_t xaudioSamplesBuffer[XAUDIO_NUM_SAMPLES * XAUDIO_NUM_CHANNELS];\n    for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++)\n    {\n        for (size_t j = 0; j < XAUDIO_NUM_CHANNELS; j++)\n        {\n            xaudioSamplesBuffer[i * XAUDIO_NUM_CHANNELS + j] = ByteSwap(((uint32_t *)samples)[j * XAUDIO_NUM_SAMPLES + i]);\n        }\n    }\n\n    g_audioDumpStream.write((const char *)(xaudioSamplesBuffer), sizeof(xaudioSamplesBuffer));\n#endif\n\n    XAudioSubmitFrame(samples);\n    return 0;\n}\n"
  },
  {
    "path": "MarathonRecomp/apu/audio.h",
    "content": "#pragma once\n\n#define XAUDIO_SAMPLES_HZ 48000\n#define XAUDIO_NUM_CHANNELS 6\n#define XAUDIO_SAMPLE_BITS 32\n\n// Number of samples in a frame\n#define XAUDIO_NUM_SAMPLES 256\n\nvoid XAudioInitializeSystem();\nvoid XAudioRegisterClient(PPCFunc* callback, uint32_t param);\nvoid XAudioSubmitFrame(void* samples);\nvoid XAudioConfigValueChangedCallback(class IConfigDef* configDef);\n\nuint32_t XAudioRegisterRenderDriverClient(be<uint32_t>* callback, be<uint32_t>* driver);\r\nuint32_t XAudioUnregisterRenderDriverClient(uint32_t driver);\r\nuint32_t XAudioSubmitRenderDriverFrame(uint32_t driver, void* samples);\n"
  },
  {
    "path": "MarathonRecomp/apu/driver/sdl2_driver.cpp",
    "content": "#include <apu/audio.h>\n#include <cpu/guest_thread.h>\n#include <kernel/heap.h>\n#include <os/logger.h>\n#include <ui/game_window.h>\n#include <user/config.h>\n\nstatic PPCFunc* g_clientCallback{};\nstatic uint32_t g_clientCallbackParam{}; // pointer in guest memory\nstatic SDL_AudioDeviceID g_audioDevice{};\nstatic bool g_downMixToStereo;\n\nstatic void CreateAudioDevice()\n{\n    if (g_audioDevice != NULL)\n        SDL_CloseAudioDevice(g_audioDevice);\n\n    bool surround = Config::ChannelConfiguration == EChannelConfiguration::Surround;\n    int allowedChanges = surround ? SDL_AUDIO_ALLOW_CHANNELS_CHANGE : 0;\n\n    SDL_AudioSpec desired{}, obtained{};\n    desired.freq = XAUDIO_SAMPLES_HZ;\n    desired.format = AUDIO_F32SYS;\n    desired.channels = surround ? XAUDIO_NUM_CHANNELS : 2;\n    desired.samples = XAUDIO_NUM_SAMPLES;\n    g_audioDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, allowedChanges);\n\n    if (obtained.channels != 2 && obtained.channels != XAUDIO_NUM_CHANNELS) // This check may fail only when surround sound is enabled.\n    {\n        SDL_CloseAudioDevice(g_audioDevice);\n        g_audioDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &obtained, 0);\n    }\n\n    if (!g_audioDevice)\n        LOGFN_ERROR(\"Failed to open audio device: {}\", SDL_GetError());\n\n    g_downMixToStereo = (obtained.channels == 2);\n}\n\nvoid XAudioInitializeSystem()\n{\n#ifdef _WIN32\n    // Force wasapi on Windows.\n    SDL_setenv(\"SDL_AUDIODRIVER\", \"wasapi\", true);\n#endif\n\n    SDL_SetHint(SDL_HINT_AUDIO_CATEGORY, \"playback\");\n    SDL_SetHint(SDL_HINT_AUDIO_DEVICE_APP_NAME, \"Marathon Recompiled\");\n\n    if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)\n    {\n        LOGFN_ERROR(\"Failed to init audio subsystem: {}\", SDL_GetError());\n        return;\n    }\n\n    CreateAudioDevice();\n}\n\nstatic std::unique_ptr<std::thread> g_audioThread;\nstatic volatile bool g_audioThreadShouldExit;\n\nstatic void AudioThread()\n{\n    using namespace std::chrono_literals;\n\n    GuestThreadContext ctx(0);\n\n    size_t channels = g_downMixToStereo ? 2 : XAUDIO_NUM_CHANNELS;\n\n    while (!g_audioThreadShouldExit)\n    {\n        uint32_t queuedAudioSize = SDL_GetQueuedAudioSize(g_audioDevice);\n        constexpr size_t MAX_LATENCY = 10;\n        const size_t callbackAudioSize = channels * XAUDIO_NUM_SAMPLES * sizeof(float);\n\n        if ((queuedAudioSize / callbackAudioSize) <= MAX_LATENCY)\n        {\n            ctx.ppcContext.r3.u32 = g_clientCallbackParam;\n            g_clientCallback(ctx.ppcContext, g_memory.base);\n        }\n\n        auto now = std::chrono::steady_clock::now();\n        constexpr auto INTERVAL = 1000000000ns * XAUDIO_NUM_SAMPLES / XAUDIO_SAMPLES_HZ;\n        auto next = now + (INTERVAL - now.time_since_epoch() % INTERVAL);\n\n        std::this_thread::sleep_for(std::chrono::floor<std::chrono::milliseconds>(next - now));\n\n        while (std::chrono::steady_clock::now() < next)\n            std::this_thread::yield();\n    }\n}\n\nstatic void CreateAudioThread()\n{\n    SDL_PauseAudioDevice(g_audioDevice, 0);\n    g_audioThreadShouldExit = false;\n    g_audioThread = std::make_unique<std::thread>(AudioThread);\n}\n\nvoid XAudioRegisterClient(PPCFunc* callback, uint32_t param)\n{\n    auto* pClientParam = static_cast<uint32_t*>(g_userHeap.Alloc(sizeof(param)));\n    ByteSwapInplace(param);\n    *pClientParam = param;\n    g_clientCallbackParam = g_memory.MapVirtual(pClientParam);\n    g_clientCallback = callback;\n\n    CreateAudioThread();\n}\n\nvoid XAudioSubmitFrame(void* samples)\n{\n    auto floatSamples = reinterpret_cast<be<float>*>(samples);\n    auto volume = Config::MasterVolume.Value;\n\n    if (Config::MuteOnFocusLost && !GameWindow::s_isFocused)\n        volume = 0.0f;\n\n    if (g_downMixToStereo)\n    {\n        // 0: left 1.0f, right 0.0f\n        // 1: left 0.0f, right 1.0f\n        // 2: left 0.75f, right 0.75f\n        // 3: left 0.0f, right 0.0f\n        // 4: left 1.0f, right 0.0f\n        // 5: left 0.0f, right 1.0f\n\n        std::array<float, 2 * XAUDIO_NUM_SAMPLES> audioFrames;\n\n        for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++)\n        {\n            float ch0 = floatSamples[0 * XAUDIO_NUM_SAMPLES + i];\n            float ch1 = floatSamples[1 * XAUDIO_NUM_SAMPLES + i];\n            float ch2 = floatSamples[2 * XAUDIO_NUM_SAMPLES + i];\n            float ch3 = floatSamples[3 * XAUDIO_NUM_SAMPLES + i];\n            float ch4 = floatSamples[4 * XAUDIO_NUM_SAMPLES + i];\n            float ch5 = floatSamples[5 * XAUDIO_NUM_SAMPLES + i];\n\n            float samp0 = (ch0 + ch2 * 0.75f + ch4) * volume;\n            float samp1 = (ch1 + ch2 * 0.75f + ch5) * volume;\n\n            audioFrames[i * 2 + 0] = isnan(samp0) ? 0.0f : samp0;\n            audioFrames[i * 2 + 1] = isnan(samp1) ? 0.0f : samp1;\n        }\n\n        SDL_QueueAudio(g_audioDevice, &audioFrames, sizeof(audioFrames));\n    }\n    else\n    {\n        std::array<float, XAUDIO_NUM_CHANNELS * XAUDIO_NUM_SAMPLES> audioFrames;\n\n        for (size_t i = 0; i < XAUDIO_NUM_SAMPLES; i++)\n        {\n            for (size_t j = 0; j < XAUDIO_NUM_CHANNELS; j++)\n            {\n                float samp = floatSamples[j * XAUDIO_NUM_SAMPLES + i] * volume;\n                audioFrames[i * 2 + j] = isnan(samp) ? 0.0f : samp;\n            }\n        }\n\n        SDL_QueueAudio(g_audioDevice, &audioFrames, sizeof(audioFrames));\n    }\n}\n\nvoid XAudioConfigValueChangedCallback(IConfigDef* configDef)\n{\n    if (configDef == &Config::ChannelConfiguration)\n    {\n        if (g_audioThread->joinable())\n        {\n            g_audioThreadShouldExit = true;\n            g_audioThread->join();\n        }\n\n        CreateAudioDevice();\n        CreateAudioThread();\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/apu/embedded_player.cpp",
    "content": "#include <apu/audio.h>\n#include <apu/embedded_player.h>\n#include <user/config.h>\n\n#include <res/music/installer.ogg.h>\n#include <res/sounds/window_open.ogg.h>\n#include <res/sounds/window_close.ogg.h>\n#include <res/sounds/cursor.ogg.h>\n#include <res/sounds/deside.ogg.h>\n#include <res/sounds/move.ogg.h>\n#include <res/sounds/main_deside.ogg.h>\n#include <res/sounds/cannot_deside.ogg.h>\n\nenum class EmbeddedSound\n{\n    WindowOpen,\n    WindowClose,\n    Cursor,\n    Deside,\n    Move,\n    MainDeside,\n    CannotDeside,\n    Count\n};\n\nstruct EmbeddedSoundData\n{\n    Mix_Chunk* chunk{};\n};\n\nstatic std::array<EmbeddedSoundData, size_t(EmbeddedSound::Count)> g_embeddedSoundData = {};\nstatic const std::unordered_map<std::string_view, EmbeddedSound> g_embeddedSoundMap =\n{\n    { \"window_open\", EmbeddedSound::WindowOpen },\n    { \"window_close\", EmbeddedSound::WindowClose },\n    { \"cursor\", EmbeddedSound::Cursor },\n    { \"deside\", EmbeddedSound::Deside },\n    { \"move\", EmbeddedSound::Move },\n    { \"main_deside\", EmbeddedSound::MainDeside },\n    { \"cannot_deside\", EmbeddedSound::CannotDeside },\n};\n\nstatic size_t g_channelIndex;\n\nstatic void PlayEmbeddedSound(EmbeddedSound s)\n{\n    EmbeddedSoundData &data = g_embeddedSoundData[size_t(s)];\n    if (data.chunk == nullptr)\n    {\n        // The sound hasn't been created yet, create it and pick it.\n        const void *soundData = nullptr;\n        size_t soundDataSize = 0;\n        switch (s)\n        {\n        case EmbeddedSound::WindowOpen:\n            soundData = g_window_open;\n            soundDataSize = sizeof(g_window_open);\n            break;\n        case EmbeddedSound::WindowClose:\n            soundData = g_window_close;\n            soundDataSize = sizeof(g_window_close);\n            break;\n        case EmbeddedSound::Cursor:\n            soundData = g_cursor;\n            soundDataSize = sizeof(g_cursor);\n            break;\n        case EmbeddedSound::Deside:\n            soundData = g_deside;\n            soundDataSize = sizeof(g_deside);\n            break;\n        case EmbeddedSound::Move:\n            soundData = g_move;\n            soundDataSize = sizeof(g_move);\n            break;\n        case EmbeddedSound::MainDeside:\n            soundData = g_main_deside;\n            soundDataSize = sizeof(g_main_deside);\n            break;\n        case EmbeddedSound::CannotDeside:\n            soundData = g_cannot_deside;\n            soundDataSize = sizeof(g_cannot_deside);\n            break;\n        default:\n            assert(false && \"Unknown embedded sound.\");\n            return;\n        }\n\n        data.chunk = Mix_LoadWAV_RW(SDL_RWFromConstMem(soundData, soundDataSize), 1);\n    }\n    \n    Mix_VolumeChunk(data.chunk, (Config::MasterVolume * Config::EffectsVolume * EmbeddedPlayer::EFFECTS_VOLUME) * MIX_MAX_VOLUME);\n    Mix_PlayChannel(g_channelIndex % MIX_CHANNELS, data.chunk, 0);\n    ++g_channelIndex;\n}\n\nstatic Mix_Music* g_installerMusic;\n\nvoid EmbeddedPlayer::Init() \n{\n    Mix_OpenAudio(XAUDIO_SAMPLES_HZ, AUDIO_F32SYS, 2, 4096);\n    g_installerMusic = Mix_LoadMUS_RW(SDL_RWFromConstMem(g_installer_music, sizeof(g_installer_music)), 1);\n\n    s_isActive = true;\n}\n\nvoid EmbeddedPlayer::Play(const char *name) \n{\n    assert(s_isActive && \"Playback shouldn't be requested if the Embedded Player isn't active.\");\n\n    auto it = g_embeddedSoundMap.find(name);\n    if (it == g_embeddedSoundMap.end())\n    {\n        return;\n    }\n\n    PlayEmbeddedSound(it->second);\n}\n\nvoid EmbeddedPlayer::PlayMusic()\n{\n    if (!Mix_PlayingMusic())\n    {\n        Mix_PlayMusic(g_installerMusic, INT_MAX);\n        Mix_VolumeMusic(Config::MasterVolume * Config::MusicVolume * MUSIC_VOLUME * MIX_MAX_VOLUME);\n    }\n}\n\nvoid EmbeddedPlayer::FadeOutMusic()\n{\n    if (Mix_PlayingMusic())\n        Mix_FadeOutMusic(1000);\n}\n\nvoid EmbeddedPlayer::Shutdown() \n{\n    for (EmbeddedSoundData &data : g_embeddedSoundData)\n    {\n        if (data.chunk != nullptr)\n            Mix_FreeChunk(data.chunk);\n    }\n\n    Mix_HaltMusic();\n    Mix_FreeMusic(g_installerMusic);\n\n    Mix_CloseAudio();\n    Mix_Quit();\n\n    s_isActive = false;\n}\n"
  },
  {
    "path": "MarathonRecomp/apu/embedded_player.h",
    "content": "#pragma once\n\nstruct EmbeddedPlayer \n{\n    // Arbitrarily picked volume to match the mixing in the original game.\n    static constexpr float MUSIC_VOLUME = 0.25f;\n    static constexpr float EFFECTS_VOLUME = 0.2f;\n\n    static inline bool s_isActive = false;\n\n    static void Init();\n    static void Play(const char *name);\n    static void PlayMusic();\n    static void FadeOutMusic();\n    static void Shutdown();\n};\n"
  },
  {
    "path": "MarathonRecomp/apu/xma_decoder.cpp",
    "content": "/**\n******************************************************************************\n* Xenia : Xbox 360 Emulator Research Project                                 *\n******************************************************************************\n* Copyright 2024 Xenia Canary. All rights reserved.                          *\n* Released under the BSD license - see LICENSE in the root for more details. *\n******************************************************************************\n*/\n\n// Almost all decoding code is from Xenia Canary, so leave the copyright here\n\n#include \"xma_decoder.h\"\n\n// #define ENABLE_DEBUG_XMA_DECODER\n\n#ifdef ENABLE_DEBUG_XMA_DECODER\n#define debug_printf(...) printf(__VA_ARGS__)\n#else\n#define debug_printf(...)\n#endif\n\nconstexpr uint32_t kOutputBytesPerBlock = 256;\nconstexpr uint32_t kBitsPerPacketHeader = 32;\nconstexpr uint32_t kBitsPerPacket = kBytesPerPacket * 8;\nconstexpr uint32_t kMaxFrameLength = 0x7FFF;\nconstexpr uint32_t kBitsPerFrameHeader = 15;\nconstexpr uint32_t kMaxFrameSizeinBits = 0x4000 - kBitsPerPacketHeader;\n\nuint32_t XMAPlaybackGetFrameOffsetFromPacketHeader(uint32_t header) {\n    uint32_t result = 0;\n\n    if (header != 0x7FFF) {\n        return ((header >> 11) & 0x7FFF) + 32;\n    }\n\n    return result;\n}\n\nint16_t GetPacketNumber(size_t size, size_t bitOffset) {\n    if (bitOffset < kBitsPerPacketHeader) {\n        return -1;\n    }\n\n    if (bitOffset >= (size << 3)) {\n        return -1;\n    }\n\n    size_t byteOffset = bitOffset >> 3;\n    size_t packetNumber = byteOffset / kBytesPerPacket;\n\n    return (int16_t)packetNumber;\n}\n\nstatic uint32_t GetPacketFrameOffset(const uint8_t *packet) {\n    uint32_t val = (uint16_t)(((packet[0] & 0x3) << 13) | (packet[1] << 5) | (packet[2] >> 3));\n    return val + 32;\n}\n\nstruct kPacketInfo {\n    uint8_t frameCount;\n    uint8_t currentFrame;\n    uint32_t currentFrameSize;\n\n    const bool IsLastFrameInPacket() const {\n        return currentFrame == frameCount - 1;\n    }\n};\n\nkPacketInfo GetPacketInfo(uint8_t *packet, uint32_t frameOffset) {\n    kPacketInfo packetInfo = {};\n\n    const uint32_t firstFrameOffset = GetPacketFrameOffset(packet);\n    BitStream stream(packet, kBitsPerPacket);\n    stream.SetOffset(firstFrameOffset);\n\n    // Handling of split frame\n    if (frameOffset < firstFrameOffset) {\n        packetInfo.currentFrame = 0;\n        packetInfo.currentFrameSize = firstFrameOffset - frameOffset;\n    }\n\n    while (true) {\n        if (stream.BitsRemaining() < kBitsPerFrameHeader) {\n            break;\n        }\n\n        const uint64_t frameSize = stream.Peek(kBitsPerFrameHeader);\n        if (frameSize == kMaxFrameLength) {\n            break;\n        }\n\n        if (stream.offset_bits() == frameOffset) {\n            packetInfo.currentFrame = packetInfo.frameCount;\n            packetInfo.currentFrameSize = (uint32_t)frameSize;\n        }\n\n        packetInfo.frameCount++;\n\n        if (frameSize > stream.BitsRemaining()) {\n            // Last frame.\n            break;\n        }\n\n        stream.Advance(frameSize - 1);\n\n        // Read the trailing bit to see if frames follow\n        if (stream.Read(1) == 0) {\n            break;\n        }\n    }\n\n    return packetInfo;\n}\n\nuint8_t *GetNextPacket(XmaPlayback *playback, uint32_t nextPacketIndex, uint32_t currentInputPacketCount) {\n    if (nextPacketIndex < currentInputPacketCount) {\n        uint8_t *currentInputBuffer = (uint8_t *)g_memory.Translate(playback->GetCurrentInputBufferAddress());\n        return currentInputBuffer + nextPacketIndex * kBytesPerPacket;\n    }\n\n    const uint8_t nextBufferIndex = playback->currentBuffer ^ 1;\n\n    if (!playback->IsInputBufferValid(nextBufferIndex)) {\n        return nullptr;\n    }\n\n    const uint32_t nextBufferAddress = playback->GetInputBufferAddress(nextBufferIndex);\n\n    if (!nextBufferAddress) {\n        // This should never occur, but there is always a chance\n        debug_printf(\"XmaContext: Buffer is marked as valid, but doesn't have valid pointer!\\n\");\n        return nullptr;\n    }\n\n    return (uint8_t *)g_memory.Translate(nextBufferAddress);\n}\n\nuint8_t GetPacketSkipCount(const uint8_t *packet) { return packet[3]; }\n\nuint32_t GetAmountOfBitsToRead(const uint32_t remainingStreamBits, const uint32_t frameSize) {\n    return std::min(remainingStreamBits, frameSize);\n}\n\ntemplate <typename T> T clamp_float(T value, T minValue, T maxValue) {\n    float clampedToMin = std::isgreater(value, minValue) ? value : minValue;\n    return std::isless(clampedToMin, maxValue) ? clampedToMin : maxValue;\n}\n\nconst uint32_t GetNextPacketReadOffset(uint8_t *buffer, uint32_t nextPacketIndex,\n                                       uint32_t currentInputPacketCount) {\n    if (nextPacketIndex >= currentInputPacketCount) {\n        return kBitsPerPacketHeader;\n    }\n\n    uint8_t *nextPacket = buffer + (nextPacketIndex * kBytesPerPacket);\n    const uint32_t packetFrameOffset = GetPacketFrameOffset(nextPacket);\n\n    if (packetFrameOffset > kMaxFrameSizeinBits) {\n        const uint32_t offset = GetNextPacketReadOffset(buffer, nextPacketIndex + 1, currentInputPacketCount);\n        return offset;\n    }\n\n    const uint32_t newInputBufferOffset = (nextPacketIndex * kBitsPerPacket) + packetFrameOffset;\n\n    return newInputBufferOffset;\n}\n\nvoid SwapInputBuffer(XmaPlayback *playback) {\n    // No more frames.\n    if (playback->currentBuffer == 0) {\n        playback->inputBuffer1Valid = 0;\n    } else {\n        playback->inputBuffer2Valid = 0;\n    }\n\n    playback->currentBuffer ^= 1;\n    playback->inputBufferReadOffset = kBitsPerPacketHeader;\n}\n\nvoid UpdateLoopStatus(XmaPlayback *playback) {\n    if (playback->numLoops == 0) {\n        return;\n    }\n\n    const uint32_t loop_start = std::max(kBitsPerPacketHeader, playback->loopStartOffset);\n    const uint32_t loop_end = std::max(kBitsPerPacketHeader, playback->loopEndOffset);\n\n    if (playback->inputBufferReadOffset != loop_end) {\n        return;\n    }\n\n    playback->inputBufferReadOffset = loop_start;\n\n    if (playback->numLoops != 255) {\n        playback->numLoops--;\n    }\n}\n\nvoid Decode(XmaPlayback *playback) {\n    if (!playback->IsAnyInputBufferValid()) {\n        return;\n    }\n\n    if (playback->currentFrameRemainingSubframes > 0) {\n        return;\n    }\n\n    uint8_t *currentInputBuffer = playback->GetCurrentInputBuffer();\n\n    playback->inputBuffer.fill(0);\n\n    UpdateLoopStatus(playback); // TODO\n\n    const uint32_t currentInputSize = playback->GetCurrentInputBufferPacketCount() * kBytesPerPacket;\n    uint32_t currentInputPacketCount = currentInputSize / kBytesPerPacket;\n    int16_t packetIndex = GetPacketNumber(currentInputSize, playback->inputBufferReadOffset);\n\n    if (packetIndex == -1) {\n        return;\n    }\n\n    uint8_t *packet = currentInputBuffer + (packetIndex * kBytesPerPacket);\n\n    const uint32_t frameOffset = GetPacketFrameOffset(packet);\n    if (playback->inputBufferReadOffset < frameOffset) {\n        playback->inputBufferReadOffset = frameOffset;\n    }\n\n    uint32_t relativeOffset = playback->inputBufferReadOffset % kBitsPerPacket;\n    const kPacketInfo packetInfo = GetPacketInfo(packet, relativeOffset);\n    const uint32_t packetToSkip = GetPacketSkipCount(packet) + 1;\n    const uint32_t nextPacketIndex = packetIndex + packetToSkip;\n\n    BitStream stream = BitStream(currentInputBuffer, (packetIndex + 1) * kBitsPerPacket);\n    stream.SetOffset(playback->inputBufferReadOffset);\n\n    const uint64_t bitsToCopy = GetAmountOfBitsToRead((uint32_t)stream.BitsRemaining(),\n                                                      packetInfo.currentFrameSize);\n\n    if (bitsToCopy == 0) {\n        SwapInputBuffer(playback);\n        return;\n    }\n\n    if (packetInfo.IsLastFrameInPacket()) {\n        // Frame is a split frame\n        if (stream.BitsRemaining() < packetInfo.currentFrameSize) {\n            const uint8_t *nextPacket = GetNextPacket(playback, nextPacketIndex, currentInputPacketCount);\n\n            if (!nextPacket) {\n                // Error path\n                // Decoder probably should return error here\n                // Not sure what error code should be returned\n                // data->error_status = 4;\n                __builtin_debugtrap();\n                return;\n            }\n\n            // Copy next packet to buffer\n            std::memcpy(playback->inputBuffer.data() + kBytesPerPacketData,\n                        nextPacket + kBytesPerPacketHeader, kBytesPerPacketData);\n        }\n    }\n\n    std::memcpy(playback->inputBuffer.data(), packet + kBytesPerPacketHeader, kBytesPerPacketData);\n\n    stream = BitStream(playback->inputBuffer.data(), (kBitsPerPacket - kBitsPerPacketHeader) * 2);\n    stream.SetOffset(relativeOffset - kBitsPerPacketHeader);\n\n    playback->xmaFrame.fill(0);\n\n    const uint32_t paddingStart = static_cast<uint8_t>(stream.Copy(playback->xmaFrame.data() + 1,\n                                                                   packetInfo.currentFrameSize));\n\n    playback->rawFrame.fill(0);\n    playback->av_packet_->data = playback->xmaFrame.data();\n    playback->av_packet_->size = static_cast<int>(1 + ((paddingStart + packetInfo.currentFrameSize) / 8) +\n                                                  (((paddingStart + packetInfo.currentFrameSize) % 8) ? 1 : 0));\n\n    auto paddingEnd = playback->av_packet_->size * 8 - (8 + paddingStart + packetInfo.currentFrameSize);\n    playback->xmaFrame[0] = ((paddingStart & 7) << 5) | ((paddingEnd & 7) << 2);\n\n    auto ret = avcodec_send_packet(playback->codec_ctx, playback->av_packet_);\n    if (ret < 0) {\n        debug_printf(\"Error sending packet for decoding: %s\\n\", av_err2str(ret));\n    }\n\n    ret = avcodec_receive_frame(playback->codec_ctx, playback->av_frame_);\n    if (ret < 0) {\n        debug_printf(\"Error receiving frame from decoder: %s\\n\", av_err2str(ret));\n    }\n\n    constexpr float scale = (1 << 15) - 1;\n    auto out = reinterpret_cast<int16_t *>(playback->rawFrame.data());\n    auto samples = reinterpret_cast<const uint8_t **>(&playback->av_frame_->data);\n\n    uint32_t o = 0;\n    if (playback->av_frame_->nb_samples != 0) {\n        for (uint32_t i = 0; i < kSamplesPerFrame; i++) {\n            for (uint32_t j = 0; j < playback->av_frame_->ch_layout.nb_channels; j++) {\n                // Select the appropriate array based on the current channel.\n                auto in = reinterpret_cast<const float *>(samples[j]);\n\n                // Raw samples sometimes aren't within [-1, 1]\n                float scaledSample = clamp_float(in[i], -1.0f, 1.0f) * scale;\n\n                // Convert the sample and output it in big endian.\n                auto sample = static_cast<int16_t>(scaledSample);\n                out[o++] = ByteSwap(sample);\n            }\n        }\n    }\n    playback->currentFrameRemainingSubframes = 4 * playback->channelCount;\n\n    if (!packetInfo.IsLastFrameInPacket()) {\n        const uint32_t nextFrameOffset = (playback->inputBufferReadOffset + bitsToCopy) % kBitsPerPacket;\n\n        playback->inputBufferReadOffset = (packetIndex * kBitsPerPacket) + nextFrameOffset;\n        return;\n    }\n\n    uint32_t nextInputOffset = GetNextPacketReadOffset(currentInputBuffer, nextPacketIndex,\n                                                       currentInputPacketCount);\n\n    if (nextInputOffset == kBitsPerPacketHeader) {\n        SwapInputBuffer(playback);\n\n        // We're at start of next buffer\n        // Any frames in this packet decoder should go to the first frame in the packet.\n        // If it doesn't have any frames, then it should immediately go to the next packet.\n        if (playback->IsAnyInputBufferValid()) {\n            nextInputOffset = GetPacketFrameOffset((uint8_t *)g_memory.Translate(\n                    playback->GetCurrentInputBufferAddress()));\n\n            if (nextInputOffset > kMaxFrameSizeinBits) {\n                SwapInputBuffer(playback);\n                return;\n            }\n        } else {\n            // HACK\n            SwapInputBuffer(playback);\n        }\n    }\n\n    playback->inputBufferReadOffset = nextInputOffset;\n}\n\nvoid Consume(XmaPlayback *playback) {\n    if (!playback->currentFrameRemainingSubframes) {\n        return;\n    }\n\n    const int8_t subframesToWrite = std::min((int8_t)playback->currentFrameRemainingSubframes,\n                                             (int8_t)playback->subframes);\n\n    const int8_t rawFrameReadOffset = ((kBytesPerFrameChannel / kOutputBytesPerBlock) * playback->channelCount)\n                                      - playback->currentFrameRemainingSubframes;\n\n    playback->outputRb.Write(playback->rawFrame.data() +\n                             (kOutputBytesPerBlock * rawFrameReadOffset),\n                             subframesToWrite * kOutputBytesPerBlock);\n    playback->remainingSubframeBlocksInOutputBuffer -= subframesToWrite;\n    playback->currentFrameRemainingSubframes -= subframesToWrite;\n}\n\nvoid DecoderThreadFunc(XmaPlayback *playback) {\n    while (playback->isRunning) {\n        std::unique_lock<std::mutex> lock(playback->mutex);\n\n        playback->cv.wait(lock, [&] {\n            return (!playback->isRunning || (playback->outputBufferValid == 1 &&\n                                             playback->IsAnyInputBufferValid())) &&\n                   !playback->isLocked.load();\n        });\n\n        if (!playback->outputBufferValid)\n            continue;\n\n        if (!playback->isRunning)\n            break;\n\n        lock.unlock();\n\n        const int32_t minimumSubframeDecodeCount = (playback->subframes * playback->channelCount) - 1;\n\n        size_t outputCapacity = playback->outputBufferBlockCount * kOutputBytesPerBlock;\n\n        const uint32_t outputReadOffset = playback->outputBufferReadOffset * kOutputBytesPerBlock;\n        const uint32_t outputWriteOffset = playback->outputBufferWriteOffset * kOutputBytesPerBlock;\n\n        playback->outputRb = RingBuffer((uint8_t *)g_memory.Translate(playback->outputBuffer),\n                                        outputCapacity);\n        playback->outputRb.set_read_offset(outputReadOffset);\n        playback->outputRb.set_write_offset(outputWriteOffset);\n        playback->remainingSubframeBlocksInOutputBuffer = (int32_t)playback->outputRb.write_count()\n                                                          / kOutputBytesPerBlock;\n\n        if (minimumSubframeDecodeCount > playback->remainingSubframeBlocksInOutputBuffer) {\n            playback->bAllowedToDecode = false;\n            lock.lock();\n            continue;\n        }\n\n        while (playback->remainingSubframeBlocksInOutputBuffer >= minimumSubframeDecodeCount) {\n            Decode(playback);\n            Consume(playback);\n\n            if (!playback->IsAnyInputBufferValid()) {\n                break;\n            }\n        }\n\n        playback->outputBufferWriteOffset = playback->outputRb.write_offset() / kOutputBytesPerBlock;\n        playback->bAllowedToDecode = false;\n\n        if (playback->outputRb.empty()) {\n            playback->outputBufferValid = 0;\n        }\n\n        lock.lock();\n    }\n}\n\nuint32_t XMAPlaybackCreate(uint32_t streams, XMAPLAYBACKINIT *init, uint32_t flags, be<uint32_t> *outPlayback) {\n    const auto xmaPlayback = g_userHeap.AllocPhysical<XmaPlayback>(\n            init->sampleRate.get(), init->outputBufferSize.get(), init->channelCount,\n            init->subframes);\n    xmaPlayback->decoderThread = std::thread(DecoderThreadFunc, xmaPlayback);\n    *outPlayback = g_memory.MapVirtual(xmaPlayback);\n\n    return 0;\n}\n\nuint32_t XMAPlaybackRequestModifyLock(XmaPlayback *playback) {\n    std::lock_guard<std::mutex> lock(playback->mutex);\n    playback->isLocked = true;\n\n    return 0;\n}\n\nuint32_t XMAPlaybackWaitUntilModifyLockObtained(XmaPlayback *playback) {\n    std::unique_lock<std::mutex> lock(playback->mutex);\n    playback->cv.wait(lock, [&playback] { return playback->isLocked.load(); });\n\n    return 0;\n}\n\nuint32_t XMAPlaybackQueryReadyForMoreData(XmaPlayback *playback, uint32_t stream) {\n    return playback->inputBuffer1Valid == 0 || playback->inputBuffer2Valid == 0;\n}\n\nuint32_t XMAPlaybackIsIdle(XmaPlayback *playback, uint32_t stream) {\n    return playback->inputBuffer1Valid == 0 && playback->inputBuffer2Valid == 0;\n}\n\nuint32_t XMAPlaybackQueryContextsAllocated(XmaPlayback *playback) {\n    if (!playback) {\n        return 0;\n    }\n\n    return 1;\n}\n\nuint32_t XMAPlaybackResumePlayback(XmaPlayback *playback) {\n    std::lock_guard<std::mutex> lock(playback->mutex);\n    playback->isLocked = false;\n    playback->cv.notify_one();\n\n    return 0;\n}\n\nuint32_t XMAPlaybackQueryInputDataPending(XmaPlayback *playback, uint32_t stream, uint32_t data) {\n    if (playback->inputBuffer1Valid && playback->inputBuffer1 == data) {\n        return 1;\n    }\n\n    if (playback->inputBuffer2Valid && playback->inputBuffer2 == data) {\n        return 1;\n    }\n\n    return 0;\n}\n\nuint32_t XMAPlaybackGetErrorBits(XmaPlayback *playback, uint32_t stream) {\n    return 0;\n}\n\nuint32_t XMAPlaybackSubmitData(XmaPlayback *playback, uint32_t stream, uint32_t data, uint32_t dataSize) {\n    if (!playback->isLocked) {\n        return 1;\n    }\n\n    std::lock_guard<std::mutex> lock(playback->mutex);\n    uint32_t packetCount = dataSize >> 11;\n\n    uint32_t validBuffer = playback->inputBuffer1Valid | (playback->inputBuffer2Valid << 1);\n\n    if (playback->inputBuffer1Valid == 1) {\n        if (playback->inputBuffer2Valid == 1) {\n            return 0x80070005;\n        }\n        playback->inputBuffer2 = data;\n        playback->inputBuffer2Size = packetCount & 0xFFF;\n\n        playback->inputBuffer2Valid = 1;\n    } else {\n        playback->inputBuffer1 = data;\n        playback->inputBuffer1Size = packetCount & 0xFFF;\n\n        playback->inputBuffer1Valid = 1;\n    }\n\n    if (!validBuffer) {\n        uint8_t *currentInputBuffer = (uint8_t *)g_memory.Translate(data);\n        uint32_t frameOffset = XMAPlaybackGetFrameOffsetFromPacketHeader(*currentInputBuffer);\n\n        if (frameOffset) {\n            playback->inputBufferReadOffset = frameOffset & 0x3FFFFFF;\n        }\n    }\n\n    playback->bAllowedToDecode = true;\n    playback->cv.notify_one();\n    return 0;\n}\n\nuint32_t XMAPlaybackQueryAvailableData(XmaPlayback *playback, uint32_t stream) {\n    if (!playback->isLocked || (playback->inputBuffer1Valid == 0 && playback->inputBuffer2Valid == 0)) {\n        return 0;\n    }\n\n    uint32_t partialBytesRead = playback->partialBytesRead;\n    uint32_t writeBufferOffsetRead = playback->outputBufferReadOffset & 0x1F;\n    uint32_t offsetWrite = playback->outputBufferWriteOffset;\n    uint32_t sizeWrite = playback->outputBufferBlockCount & 0x1F;\n\n    uint32_t availableBytes = 0;\n    uint32_t isValidWrite = playback->outputBufferValid;\n\n    if (partialBytesRead) {\n        availableBytes = 256 - partialBytesRead;\n        writeBufferOffsetRead++;\n        isValidWrite = 1;\n    }\n\n    uint32_t availableBlocks = 0;\n    if (offsetWrite <= writeBufferOffsetRead) {\n        if (offsetWrite < writeBufferOffsetRead || !isValidWrite) {\n            availableBlocks = sizeWrite - writeBufferOffsetRead;\n        }\n    } else {\n        availableBlocks = offsetWrite - writeBufferOffsetRead;\n    }\n\n    uint32_t totalBytes = (availableBlocks << 8) + availableBytes;\n    uint32_t bytesPerSample = playback->channelCount;\n\n    return totalBytes >> bytesPerSample;\n}\n\n\nuint32_t XMAPlaybackAccessDecodedData(XmaPlayback *playback, uint32_t stream, uint32_t **data) {\n    if (!playback->isLocked)\n        return 0;\n\n    uint32_t partialBytesRead = playback->partialBytesRead;\n    uint32_t addr = reinterpret_cast<uint32_t>(\n            playback->outputBuffer +\n            ((playback->outputBufferReadOffset << 8) & 0x1F00) +\n            partialBytesRead);\n    ;\n    *data = (uint32_t *)__builtin_bswap32(addr);\n\n    uint32_t writeBufferOffsetRead = playback->outputBufferReadOffset & 0x1F;\n    uint32_t offsetWrite = playback->outputBufferWriteOffset;\n    uint32_t sizeWrite = playback->outputBufferBlockCount & 0x1F;\n\n    uint32_t availableBytes = 0;\n    uint32_t isValidWrite = playback->outputBufferValid;\n\n    if (partialBytesRead) {\n        availableBytes = 256 - partialBytesRead;\n        writeBufferOffsetRead++;\n        isValidWrite = 1;\n    }\n\n    uint32_t availableBlocks = 0;\n    if (offsetWrite <= writeBufferOffsetRead) {\n        if (offsetWrite < writeBufferOffsetRead || !isValidWrite) {\n            availableBlocks = sizeWrite - writeBufferOffsetRead;\n        }\n    } else {\n        availableBlocks = offsetWrite - writeBufferOffsetRead;\n    }\n\n    uint32_t totalBytes = (availableBlocks << 8) + availableBytes;\n    uint32_t bytesPerSample = playback->channelCount;\n\n    return totalBytes >> bytesPerSample;\n}\n\nuint32_t XMAPlaybackConsumeDecodedData(XmaPlayback *playback, uint32_t stream, uint32_t maxSamples, uint32_t **data) {\n    if (!playback->isLocked) {\n        return 0;\n    }\n\n    uint32_t totalBytes = 0;\n    uint32_t partialBytesRead = playback->partialBytesRead;\n    uint32_t addr = reinterpret_cast<uint32_t>(\n            playback->outputBuffer +\n            ((playback->outputBufferReadOffset << 8) & 0x1F00) +\n            partialBytesRead);\n\n    *data = (uint32_t *)__builtin_bswap32(addr);\n    uint32_t bytesPerSample = playback->channelCount;\n    uint32_t bytesDesired = maxSamples << bytesPerSample;\n    if (partialBytesRead) {\n        if (bytesDesired < 256 - partialBytesRead) {\n            totalBytes = bytesDesired;\n            playback->partialBytesRead += bytesDesired;\n            bytesDesired = 0;\n        } else {\n            totalBytes = 256 - partialBytesRead;\n            bytesDesired -= 256 - partialBytesRead;\n            playback->partialBytesRead = 0;\n\n            uint32_t writeIndex = (playback->outputBufferReadOffset + 1) & 0x1F;\n\n            if (writeIndex >= (playback->outputBufferBlockCount & 0x1F)) {\n                writeIndex = 0;\n            }\n\n            playback->outputBufferReadOffset = writeIndex;\n            playback->outputBufferValid = 1;\n        }\n    }\n\n    uint32_t writeIndex = playback->outputBufferReadOffset;\n    uint32_t blocksToProcess = bytesDesired >> 8;\n    uint32_t availableBlocks = 0;\n\n    uint32_t writeSize = playback->outputBufferBlockCount;\n    if (writeSize <= writeIndex) {\n        if (writeSize < writeIndex || !playback->outputBufferValid) {\n            availableBlocks = (playback->outputBufferBlockCount & 0x1F) - writeIndex;\n        }\n    } else {\n        availableBlocks = writeSize - writeIndex;\n    }\n\n    if (blocksToProcess) {\n        if (blocksToProcess > availableBlocks) {\n            blocksToProcess = availableBlocks;\n        }\n\n        totalBytes += blocksToProcess << 8;\n        availableBlocks -= blocksToProcess;\n        writeIndex = (writeIndex + blocksToProcess) & 0x1F;\n\n        if (writeIndex >= (playback->outputBufferBlockCount & 0x1F)) {\n            writeIndex = 0;\n        }\n\n        playback->outputBufferReadOffset = writeIndex;\n        playback->outputBufferValid = 1;\n    }\n\n    uint32_t remainingBytes = bytesDesired & 0xFF;\n    if (remainingBytes && availableBlocks) {\n        totalBytes += remainingBytes;\n        playback->partialBytesRead = remainingBytes;\n    }\n\n    // playback->bAllowedToDecode = true;\n    // playback->cv.notify_one();\n\n    uint32_t samplesConsumed = totalBytes >> bytesPerSample;\n    playback->streamPosition += samplesConsumed;\n\n    return samplesConsumed;\n}\n\nuint32_t XMAPlaybackQueryModifyLockObtained(XmaPlayback *playback) {\n    debug_printf(\"XMAPlaybackQueryModifyLockObtained %x\\n\", playback);\n    return playback->isLocked.load();\n}\n\nuint32_t XMAPlaybackDestroy(XmaPlayback *playback) {\n    debug_printf(\"XMAPlaybackDestroy %x\\n\", playback);\n    return 0;\n}\n\nuint32_t XMAPlaybackFlushData(XmaPlayback *playback) {\n    debug_printf(\"XMAPlaybackFlushData %x\\n\", playback);\n    // __builtin_debugtrap();\n    return 0;\n}\n\nstruct XMAPLAYBACKLOOP {\n    be<uint32_t> loopStartOffset;\n    be<uint32_t> loopEndOffset;\n    uint8_t loopSubframeEnd;\n    uint8_t loopSubframeSkip;\n    uint8_t numLoops;\n    uint8_t reserved;\n};\n\nuint32_t XmaPlaybackSetLoop(XmaPlayback *playback, uint32_t streamIndex, XMAPLAYBACKLOOP *loop) {\n    playback->numLoops = loop->numLoops;\n    playback->loopSubframeEnd = loop->loopSubframeEnd;\n    playback->loopSubframeSkip = loop->loopSubframeSkip;\n    playback->loopStartOffset = loop->loopStartOffset.get() & 0x3FFFFFF;\n    playback->loopEndOffset = loop->loopEndOffset.get() & 0x3FFFFFF;\n\n    return 0;\n}\n\nuint32_t XMAPlaybackGetRemainingLoopCount(XmaPlayback *playback) {\n    debug_printf(\"XMAPlaybackGetRemainingLoopCount %x\\n\", playback);\n    __builtin_debugtrap();\n    return 0;\n}\n\nuint32_t XMAPlaybackGetStreamPosition(XmaPlayback *playback) {\n    return playback->streamPosition;\n}\n\nuint32_t XMAPlaybackSetDecodePosition(XmaPlayback *playback, uint32_t streamIndex, uint32_t bitOffset,\n                                      uint32_t subframe) {\n    playback->inputBufferReadOffset = bitOffset & 0x3FFFFFF;\n    playback->numSubframesToSkip = subframe & 0x7;\n    return 0;\n}\n\nuint32_t XMAPlaybackRewindDecodePosition(XmaPlayback *playback, uint32_t streamIndex, uint32_t numSamples) {\n    uint32_t shift = 7 - (1 != 0);\n    uint32_t adjustedSamples = numSamples >> shift;\n    uint32_t writeSize = playback->outputBufferBlockCount & 0x1F;\n\n    uint32_t newOffset;\n    if (adjustedSamples >= writeSize) {\n        newOffset = playback->inputBufferReadOffset & 0x3FFFFFF;\n        playback->outputBufferValid = 1;\n        playback->outputBufferWriteOffset = newOffset >> 27;\n        return 0;\n    }\n\n    newOffset = (playback->outputBufferWriteOffset - adjustedSamples + writeSize) & 0x1F;\n\n    if (newOffset >= writeSize) {\n        newOffset -= writeSize;\n    }\n\n    playback->outputBufferValid = 1;\n    playback->outputBufferWriteOffset = newOffset;\n\n    return 1;\n}\n\nuint32_t XMAPlaybackQueryCurrentPosition(XmaPlayback *playback) {\n    debug_printf(\"XMAPlaybackQueryCurrentPosition %x\\n\", playback);\n    __builtin_debugtrap();\n    return 0;\n}\n\nGUEST_FUNCTION_HOOK(sub_8255C090, XMAPlaybackCreate);\nGUEST_FUNCTION_HOOK(sub_8255CC48, XMAPlaybackRequestModifyLock);\nGUEST_FUNCTION_HOOK(sub_8255CCC8, XMAPlaybackWaitUntilModifyLockObtained);\nGUEST_FUNCTION_HOOK(sub_8255C4D0, XMAPlaybackQueryReadyForMoreData);\nGUEST_FUNCTION_HOOK(sub_8255C520, XMAPlaybackIsIdle);\nGUEST_FUNCTION_HOOK(sub_8255C388, XMAPlaybackQueryContextsAllocated);\nGUEST_FUNCTION_HOOK(sub_8255CF10, XMAPlaybackResumePlayback);\nGUEST_FUNCTION_HOOK(sub_8255C470, XMAPlaybackQueryInputDataPending);\nGUEST_FUNCTION_HOOK(sub_8255C9A0, XMAPlaybackGetErrorBits);\nGUEST_FUNCTION_HOOK(sub_8255C398, XMAPlaybackSubmitData);\nGUEST_FUNCTION_HOOK(sub_8255C578, XMAPlaybackQueryAvailableData);\nGUEST_FUNCTION_HOOK(sub_8255C7A8, XMAPlaybackAccessDecodedData);\nGUEST_FUNCTION_HOOK(sub_8255C5F0, XMAPlaybackConsumeDecodedData);\nGUEST_FUNCTION_HOOK(sub_8255CD90, XMAPlaybackQueryModifyLockObtained);\nGUEST_FUNCTION_HOOK(sub_8255C8D8, XMAPlaybackFlushData);\nGUEST_FUNCTION_HOOK(sub_8255C9D8, XmaPlaybackSetLoop);\nGUEST_FUNCTION_HOOK(sub_8255CA50, XMAPlaybackGetRemainingLoopCount);\nGUEST_FUNCTION_HOOK(sub_8255CA90, XMAPlaybackGetStreamPosition);\nGUEST_FUNCTION_HOOK(sub_8255CB20, XMAPlaybackSetDecodePosition);\nGUEST_FUNCTION_HOOK(sub_8255C850, XMAPlaybackRewindDecodePosition);\nGUEST_FUNCTION_HOOK(sub_8255CAB0, XMAPlaybackQueryCurrentPosition);\nGUEST_FUNCTION_HOOK(sub_8255C2C0, XMAPlaybackDestroy);\n"
  },
  {
    "path": "MarathonRecomp/apu/xma_decoder.h",
    "content": "#pragma once\n\n#include <utils/bit_stream.h>\n#include <utils/ring_buffer.h>\n#include <kernel/function.h>\n#include <kernel/heap.h>\n#include <condition_variable>\n\nextern \"C\" {\n    #include <libavcodec/avcodec.h>\n}\n\nstruct XMAPLAYBACKINIT {\n    be<uint32_t> sampleRate;\n    be<uint32_t> outputBufferSize;\n    uint8_t channelCount;\n    uint8_t subframes;\n};\n\nconstexpr uint32_t kBytesPerPacket = 2048;\nconstexpr uint32_t kBytesPerPacketHeader = 4;\nconstexpr uint32_t kBytesPerPacketData = kBytesPerPacket - kBytesPerPacketHeader;\nconstexpr uint32_t kBytesPerSample = 2;\nconstexpr uint32_t kSamplesPerFrame = 512;\nconstexpr uint32_t kBytesPerFrameChannel = kSamplesPerFrame * kBytesPerSample;\n\nstruct XmaPlayback {\n    uint32_t sampleRate;\n    uint32_t outputBufferSize;\n    uint32_t channelCount;\n    uint32_t subframes;\n    uint32_t outputBuffer;\n    uint32_t currentDataSize;\n\n    uint32_t partialBytesRead = 0;\n    uint32_t streamPosition = 0;\n\n    bool bAllowedToDecode = false;\n\n    // ffmpeg\n    AVCodecContext *codec_ctx = nullptr;\n    const AVCodec *codec = nullptr;\n    AVPacket *av_packet_ = nullptr;\n    AVFrame *av_frame_ = nullptr;\n\n    // xenia\n    std::array<uint8_t, kBytesPerPacketData * 2> inputBuffer;\n    std::array<uint8_t, 1 + 4096> xmaFrame;\n    std::array<uint8_t, kBytesPerFrameChannel * 2> rawFrame;\n    uint32_t outputBufferBlockCount = 0;\n    uint32_t outputBufferReadOffset = 0;\n    uint32_t outputBufferWriteOffset = 0;\n    int32_t remainingSubframeBlocksInOutputBuffer = 0;\n    uint8_t currentFrameRemainingSubframes = 0;\n    uint32_t inputBufferReadOffset = 32;\n\n    uint32_t numSubframesToSkip = 0;\n\n    RingBuffer outputRb;\n    uint32_t inputBuffer1 = 0;\n    uint32_t inputBuffer2 = 0;\n    size_t inputBuffer1Size = 0;\n    size_t inputBuffer2Size = 0;\n    uint32_t validInputBuffer = 0;\n    uint32_t inputBuffer1Valid = 0;\n    uint32_t inputBuffer2Valid = 0;\n    uint32_t currentBuffer = 0;\n\n    uint32_t outputBufferValid = 1;\n\n    uint8_t numLoops = 0;\n    uint8_t loopSubframeEnd = 0;\n    uint8_t loopSubframeSkip = 0;\n    uint32_t loopStartOffset = 0;\n    uint32_t loopEndOffset = 0;\n\n    std::thread decoderThread;\n    std::mutex mutex;\n    std::condition_variable cv;\n    std::atomic<bool> isLocked { false };\n    std::atomic<bool> isRunning { true };\n\n    XmaPlayback(uint32_t sampleRate, uint32_t outputBufferSize,\n                uint32_t channelCount, uint32_t subframes)\n                : sampleRate(sampleRate), outputBufferSize(outputBufferSize),\n                  channelCount(channelCount), subframes(subframes),\n                  outputRb(nullptr, 0) {\n        outputBufferBlockCount =\n                (((channelCount * outputBufferSize) << 15) & 0x7C00000) >> 22;\n        outputBuffer =\n                g_memory.MapVirtual(g_userHeap.AllocPhysical((size_t)0x2000, 0));\n\n        codec = avcodec_find_decoder(AV_CODEC_ID_XMAFRAMES);\n        if (!codec) {\n            throw std::runtime_error(\"Decoder not found\");\n        }\n\n        codec_ctx = avcodec_alloc_context3(codec);\n        if (!codec_ctx) {\n            throw std::runtime_error(\"Failed to allocate codec context\");\n        }\n\n        codec_ctx->sample_rate = sampleRate;\n        codec_ctx->ch_layout.nb_channels = channelCount;\n\n        av_frame_ = av_frame_alloc();\n        if (!av_frame_) {\n            throw std::runtime_error(\"Coulnd't allocate frame\");\n        }\n\n        if (int err = avcodec_open2(codec_ctx, codec, nullptr); err < 0) {\n            throw std::runtime_error(\"Failed to open codec\");\n        }\n        av_packet_ = av_packet_alloc();\n    }\n\n    const uint32_t GetInputBufferAddress(uint8_t bufferIndex) const {\n        return bufferIndex == 0 ? inputBuffer1 : inputBuffer2;\n    }\n\n    const uint32_t GetCurrentInputBufferAddress() const {\n        return GetInputBufferAddress(currentBuffer);\n    }\n\n    const uint32_t GetInputBufferPacketCount(uint8_t bufferIndex) const {\n        return bufferIndex == 0 ? inputBuffer1Size : inputBuffer2Size;\n    }\n    const uint32_t GetCurrentInputBufferPacketCount() const {\n        return GetInputBufferPacketCount(currentBuffer);\n    }\n\n    uint8_t *GetCurrentInputBuffer() {\n        return (uint8_t *)g_memory.Translate(GetCurrentInputBufferAddress());\n    }\n\n    bool IsInputBufferValid(uint8_t bufferIndex) const {\n        return bufferIndex == 0 ? inputBuffer1Valid : inputBuffer2Valid;\n    }\n\n    bool IsCurrentInputBufferValid() const {\n        return IsInputBufferValid(currentBuffer);\n    }\n\n    bool IsAnyInputBufferValid() const {\n        return inputBuffer1Valid || inputBuffer2Valid;\n    }\n\n    ~XmaPlayback() {\n        {\n            std::lock_guard<std::mutex> lock(mutex);\n            isRunning = false;\n            cv.notify_one();\n        }\n\n        if (decoderThread.joinable()) {\n            decoderThread.join();\n        }\n    }\n};\n"
  },
  {
    "path": "MarathonRecomp/cpu/guest_stack_var.h",
    "content": "#pragma once\n\n#include \"ppc_context.h\"\n#include <kernel/memory.h>\n\n// DO NOT use this type as anything other than a local variable.\n// This includes returning. It'll cause memory to leak in the guest stack!\n\ntemplate<typename T, bool Init = true>\nclass guest_stack_var\n{\nprivate:\n    uint32_t m_ptr = NULL;\n    uint32_t m_oldStackPtr = NULL;\n\n    void AllocGuestStackMemory()\n    {\n        auto ctx = GetPPCContext();\n        m_oldStackPtr = ctx->r1.u32;\n        m_ptr = (ctx->r1.u32 - sizeof(T)) & ~(std::max<uint32_t>(alignof(T), 8) - 1);\n        ctx->r1.u32 = m_ptr;\n    }\n\npublic:\n    T* get()\n    {\n        return reinterpret_cast<T*>(g_memory.Translate(m_ptr));\n    }\n\n    const T* get() const\n    {\n        return reinterpret_cast<const T*>(g_memory.Translate(m_ptr));\n    }\n\n    template<typename... Args>\n    guest_stack_var(Args&&... args)\n    {\n        AllocGuestStackMemory();\n\n        if (Init)\n            new (get()) T(std::forward<Args>(args)...);\n    }\n\n    guest_stack_var(const guest_stack_var<T>& other)\n    {\n        AllocGuestStackMemory();\n\n        if (Init)\n            new (get()) T(*other->get());\n    }\n\n    guest_stack_var(guest_stack_var<T>&& other)\n    {\n        AllocGuestStackMemory();\n\n        if (Init)\n            new (get()) T(std::move(*other->get()));\n    }\n\n    ~guest_stack_var()\n    {\n        get()->~T();\n\n        auto ctx = GetPPCContext();\n        // This assert will fail if the type was used as anything other than a local variable.\n        assert(ctx->r1.u32 == m_ptr);\n        ctx->r1.u32 = m_oldStackPtr;\n    }\n\n    void operator=(const guest_stack_var<T>& other)\n    {\n        if (this != &other)\n            *get() = *other->get();\n    }\n\n    void operator=(guest_stack_var<T>&& other)\n    {\n        if (this != &other)\n            *get() = std::move(*other->get());\n    }\n\n    void operator=(const T& other)\n    {\n        if (get() != &other)\n            *get() = *other;\n    }\n\n    void operator=(T&& other)\n    {\n        if (get() != &other)\n            *get() = std::move(*other);\n    }\n\n    operator const T* () const\n    {\n        return get();\n    }\n\n    operator T* ()\n    {\n        return get();\n    }\n\n    const T* operator->() const\n    {\n        return get();\n    }\n\n    T* operator->()\n    {\n        return get();\n    }\n\n    const T& operator*() const\n    {\n        return *get();\n    }\n\n    T& operator*()\n    {\n        return *get();\n    }\n};\n"
  },
  {
    "path": "MarathonRecomp/cpu/guest_thread.cpp",
    "content": "#include <cstdio>\n#include <stdafx.h>\n#include \"guest_thread.h\"\n#include <kernel/memory.h>\n#include <kernel/heap.h>\n#include <kernel/function.h>\n#include \"ppc_context.h\"\n\nconstexpr size_t PCR_SIZE = 0xAB0;\nconstexpr size_t TLS_SIZE = 0x100;\nconstexpr size_t TEB_SIZE = 0x2E0;\nconstexpr size_t STACK_SIZE = 0x80000;\nconstexpr size_t TOTAL_SIZE = PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE;\n\nconstexpr size_t TEB_OFFSET = PCR_SIZE + TLS_SIZE;\n\nGuestThreadContext::GuestThreadContext(uint32_t cpuNumber)\n{\n    assert(thread == nullptr);\n\n    thread = (uint8_t*)g_userHeap.Alloc(TOTAL_SIZE);\n    // printf(\"TOTAL_SIZE: %x %x %d\\n\", thread, TOTAL_SIZE, TOTAL_SIZE);\n    memset(thread, 0, TOTAL_SIZE);\n\n    *(uint32_t*)thread = ByteSwap(g_memory.MapVirtual(thread + PCR_SIZE)); // tls pointer\n    *(uint32_t*)(thread + 0x100) = ByteSwap(g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE)); // teb pointer\n    *(thread + 0x10C) = cpuNumber;\n\n    *(uint32_t*)(thread + PCR_SIZE + 0x10) = 0xFFFFFFFF; // that one TLS entry that felt quirky\n    *(uint32_t*)(thread + PCR_SIZE + TLS_SIZE + 0x14C) = ByteSwap(GuestThread::GetCurrentThreadId()); // thread id\n\n    ppcContext.r1.u64 = g_memory.MapVirtual(thread + PCR_SIZE + TLS_SIZE + TEB_SIZE + STACK_SIZE); // stack pointer\n    ppcContext.r13.u64 = g_memory.MapVirtual(thread);\n    ppcContext.fpscr.loadFromHost();\n\n    assert(GetPPCContext() == nullptr);\n    SetPPCContext(ppcContext);\n}\n\nGuestThreadContext::~GuestThreadContext()\n{\n    g_userHeap.Free(thread);\n}\n\n#ifdef USE_PTHREAD\nstatic size_t GetStackSize()\n{\n    // Cache as this should not change.\n    static size_t stackSize = 0;\n    if (stackSize == 0)\n    {\n        // 8 MiB is a typical default.\n        constexpr auto defaultSize = 8 * 1024 * 1024;\n        struct rlimit lim;\n        const auto ret = getrlimit(RLIMIT_STACK, &lim);\n        if (ret == 0 && lim.rlim_cur < defaultSize)\n        {\n            // Use what the system allows.\n            stackSize = lim.rlim_cur;\n        }\n        else\n        {\n            stackSize = defaultSize;\n        }\n    }\n    return stackSize;\n}\n\nstatic void* GuestThreadFunc(void* arg)\n{\n    GuestThreadHandle* hThread = (GuestThreadHandle*)arg;\n#else\nstatic void* GuestThreadFunc(GuestThreadHandle* hThread)\n{\n#endif\n    hThread->suspended.wait(true);\n    GuestThread::Start(hThread->params);\n    // HACK(1)\n    hThread->isFinished = true;\n    return nullptr;\n}\n\nGuestThreadHandle::GuestThreadHandle(const GuestThreadParams& params)\n    : params(params), suspended((params.flags & 0x1) != 0)\n#ifdef USE_PTHREAD\n{\n    pthread_attr_t attr;\n    pthread_attr_init(&attr);\n    pthread_attr_setstacksize(&attr, GetStackSize());\n    const auto ret = pthread_create(&thread, &attr, GuestThreadFunc, this);\n    if (ret != 0) {\n        fprintf(stderr, \"pthread_create failed with error code 0x%X.\\n\", ret);\n        return;\n    }\n}\n#else\n, thread(GuestThreadFunc, this)\n{\n}\n#endif\n\nGuestThreadHandle::~GuestThreadHandle()\n{\n#ifdef USE_PTHREAD\n    pthread_join(thread, nullptr);\n#else\n    if (thread.joinable())\n        thread.join();\n#endif\n}\n\ntemplate <typename ThreadType>\nstatic uint32_t CalcThreadId(const ThreadType& id)\n{\n    if constexpr (sizeof(id) == 4)\n        return *reinterpret_cast<const uint32_t*>(&id);\n    else\n        return XXH32(&id, sizeof(id), 0);\n}\n\nuint32_t GuestThreadHandle::GetThreadId() const\n{\n#ifdef USE_PTHREAD\n    return CalcThreadId(thread);\n#else\n    return CalcThreadId(thread.get_id());\n#endif\n}\n\nuint32_t GuestThreadHandle::Wait(uint32_t timeout)\n{\n    if (timeout == INFINITE || isFinished.load()) // HACK(1): isFinished\n    {\n#ifdef USE_PTHREAD\n        pthread_join(thread, nullptr);\n#else\n        if (thread.joinable())\n            thread.join();\n#endif\n\n        return STATUS_WAIT_0;\n    }\n    else if (timeout == 0)\n    {\n#ifndef USE_PTHREAD\n        if (thread.joinable())\n            return STATUS_TIMEOUT;\n#endif\n\n        return STATUS_WAIT_0;\n    }\n    else\n    {\n#ifdef USE_PTHREAD\n        pthread_join(thread, nullptr);\n#else\n        auto start = std::chrono::steady_clock::now();\n        while (thread.joinable())\n        {\n            auto elapsed = std::chrono::steady_clock::now() - start;\n            if (std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count() >= timeout)\n                return STATUS_TIMEOUT;\n\n            std::this_thread::sleep_for(std::chrono::milliseconds(1));\n        }\n#endif\n\n        return STATUS_WAIT_0;\n    }\n}\n\nuint32_t GuestThread::Start(const GuestThreadParams& params)\n{\n    const auto procMask = (uint8_t)(params.flags >> 24);\n    const auto cpuNumber = procMask == 0 ? 0 : 7 - std::countl_zero(procMask);\n\n    GuestThreadContext ctx(cpuNumber);\n    ctx.ppcContext.r3.u64 = params.value;\n\n    g_memory.FindFunction(params.function)(ctx.ppcContext, g_memory.base);\n\n    return ctx.ppcContext.r3.u32;\n}\n\nGuestThreadHandle* GuestThread::Start(const GuestThreadParams& params, uint32_t* threadId)\n{\n    auto hThread = CreateKernelObject<GuestThreadHandle>(params);\n\n    if (threadId != nullptr)\n    {\n        *threadId = hThread->GetThreadId();\n    }\n\n    return hThread;\n}\n\nuint32_t GuestThread::GetCurrentThreadId()\n{\n#ifdef USE_PTHREAD\n    return CalcThreadId(pthread_self());\n#else\n    return CalcThreadId(std::this_thread::get_id());\n#endif\n}\n\nvoid GuestThread::SetLastError(uint32_t error)\n{\n    auto* thread = (char*)g_memory.Translate(GetPPCContext()->r13.u32);\n    if (*(uint32_t*)(thread + 0x150))\n    {\n        // Program doesn't want errors\n        return;\n    }\n\n    // TEB + 0x160 : Win32LastError\n    *(uint32_t*)(thread + TEB_OFFSET + 0x160) = ByteSwap(error);\n}\n\n#ifdef _WIN32\nvoid GuestThread::SetThreadName(uint32_t threadId, const char* name)\n{\n#pragma pack(push,8)\n    const DWORD MS_VC_EXCEPTION = 0x406D1388;\n\n    typedef struct tagTHREADNAME_INFO\n    {\n        DWORD dwType; // Must be 0x1000.\n        LPCSTR szName; // Pointer to name (in user addr space).\n        DWORD dwThreadID; // Thread ID (-1=caller thread).\n        DWORD dwFlags; // Reserved for future use, must be zero.\n    } THREADNAME_INFO;\n#pragma pack(pop)\n\n    THREADNAME_INFO info;\n    info.dwType = 0x1000;\n    info.szName = name;\n    info.dwThreadID = threadId;\n    info.dwFlags = 0;\n\n    __try\n    {\n        RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);\n    }\n    __except (EXCEPTION_EXECUTE_HANDLER)\n    {\n    }\n}\n#endif\n\nvoid SetThreadNameImpl(uint32_t a1, uint32_t threadId, uint32_t* name)\n{\n#ifdef _WIN32\n    GuestThread::SetThreadName(threadId, (const char*)g_memory.Translate(ByteSwap(*name)));\n#endif\n}\n\nint GetThreadPriorityImpl(GuestThreadHandle* hThread)\n{\n#ifdef _WIN32\n    return GetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle());\n#else \n    return 0;\n#endif\n}\n\nuint32_t SetThreadIdealProcessorImpl(GuestThreadHandle* hThread, uint32_t dwIdealProcessor)\n{\n    return 0;\n}\n\n// GUEST_FUNCTION_HOOK(sub_82DFA2E8, SetThreadNameImpl);\n// GUEST_FUNCTION_HOOK(sub_82BD57A8, GetThreadPriorityImpl);\nGUEST_FUNCTION_HOOK(sub_82537F80, SetThreadIdealProcessorImpl);\n\n// GUEST_FUNCTION_STUB(sub_82BD58F8); // Some function that updates the TEB, don't really care since the field is not set"
  },
  {
    "path": "MarathonRecomp/cpu/guest_thread.h",
    "content": "#pragma once\n\n#include <kernel/xdm.h>\n\n// Use pthreads directly on macOS to be able to increase default stack size.\n#ifdef __APPLE__\n#define USE_PTHREAD 1\n#endif\n\n#ifdef USE_PTHREAD\n#include <pthread.h>\n#endif\n\n#define CURRENT_THREAD_HANDLE uint32_t(-2)\n\nstruct GuestThreadContext\n{\n    PPCContext ppcContext{};\n    uint8_t* thread = nullptr;\n\n    GuestThreadContext(uint32_t cpuNumber);\n    ~GuestThreadContext();\n};\n\nstruct GuestThreadParams\n{\n    uint32_t function;\n    uint32_t value;\n    uint32_t flags;\n};\n\nstruct GuestThreadHandle : KernelObject\n{\n    GuestThreadParams params;\n    std::atomic<bool> suspended;\n    #ifdef USE_PTHREAD\n    pthread_t thread;\n    #else\n    std::thread thread;\n    #endif\n    // HACK(1)\n    std::atomic<bool> isFinished = false;\n\n    GuestThreadHandle(const GuestThreadParams& params);\n    ~GuestThreadHandle() override;\n\n    uint32_t GetThreadId() const;\n\n    uint32_t Wait(uint32_t timeout) override;\n};\n\nstruct GuestThread\n{\n    static uint32_t Start(const GuestThreadParams& params);\n    static GuestThreadHandle* Start(const GuestThreadParams& params, uint32_t* threadId);\n\n    static uint32_t GetCurrentThreadId();\n    static void SetLastError(uint32_t error);\n\n#ifdef _WIN32\n    static void SetThreadName(uint32_t threadId, const char* name);\n#endif\n};\n"
  },
  {
    "path": "MarathonRecomp/cpu/ppc_context.h",
    "content": "#pragma once\n\ninline thread_local PPCContext* g_ppcContext;\n\ninline PPCContext* GetPPCContext()\n{\n    return g_ppcContext;\n}\n\ninline void SetPPCContext(PPCContext& ctx)\n{\n    g_ppcContext = &ctx;\n}\n"
  },
  {
    "path": "MarathonRecomp/decompressor.h",
    "content": "#pragma once\n\ntemplate<size_t N>\ninline std::unique_ptr<uint8_t[]> decompressZstd(const uint8_t(&data)[N], size_t decompressedSize)\n{\n    auto decompressedData = std::make_unique<uint8_t[]>(decompressedSize);\n    ZSTD_decompress(decompressedData.get(), decompressedSize, data, N);\n    return decompressedData;\n}\n"
  },
  {
    "path": "MarathonRecomp/exports.cpp",
    "content": "#include \"exports.h\"\n#include <apu/embedded_player.h>\n#include <kernel/function.h>\n#include <kernel/heap.h>\n#include <app.h>\n\nvoid Game_PlaySound(const char* pName)\n{\n     if (EmbeddedPlayer::s_isActive)\n     {\n         EmbeddedPlayer::Play(pName);\n     }\n     else\n     {\n         Game_PlaySound(\"system\", pName);\n     }\n}\n\nvoid Game_PlaySound(const char* pBankName, const char* pName)\n{\n    auto pBankNameGuest = g_userHeap.Alloc(strlen(pBankName) + 1);\n    auto pNameGuest = g_userHeap.Alloc(strlen(pName) + 1);\n\n    strcpy((char*)pBankNameGuest, pBankName);\n    strcpy((char*)pNameGuest, pName);\n\n    GuestToHostFunction<int>(sub_824C7868, App::s_pApp->m_pDoc->m_pRootTask.get(), pBankNameGuest, pNameGuest);\n\n    g_userHeap.Free(pBankNameGuest);\n    g_userHeap.Free(pNameGuest);\n}\n"
  },
  {
    "path": "MarathonRecomp/exports.h",
    "content": "#pragma once\n\nvoid Game_PlaySound(const char* pName);\nvoid Game_PlaySound(const char* pBankName, const char* pName);\n"
  },
  {
    "path": "MarathonRecomp/framework.h",
    "content": "#pragma once\n\n#include <codecvt>\n\n#define PROC_ADDRESS(libraryName, procName) \\\n    GetProcAddress(LoadLibrary(TEXT(libraryName)), procName)\n\n#define LIB_FUNCTION(returnType, libraryName, procName, ...) \\\n    typedef returnType _##procName(__VA_ARGS__); \\\n    _##procName* procName = (_##procName*)PROC_ADDRESS(libraryName, #procName);\n\n#define STR(x) #x\n\ntemplate<typename T>\ninline T RoundUp(const T& in_rValue, uint32_t in_round)\n{\n    return (in_rValue + in_round - 1) & ~(in_round - 1);\n}\n\ntemplate<typename T>\ninline T RoundDown(const T& in_rValue, uint32_t in_round)\n{\n    return in_rValue & ~(in_round - 1);\n}\n\ninline size_t StringHash(const std::string_view& str)\n{\n    return XXH3_64bits(str.data(), str.size());\n}\n\ntemplate<typename TValue>\nconstexpr size_t FirstBitLow(TValue value)\n{\n    constexpr size_t nbits = sizeof(TValue) * 8;\n    constexpr auto zero = TValue{};\n    constexpr auto one = static_cast<TValue>(1);\n\n    for (size_t i = 0; i < nbits; i++)\n    {\n        if ((value & (one << i)) != zero)\n        {\n            return i;\n        }\n    }\n\n    return 0;\n}\n\ninline std::unique_ptr<uint8_t[]> ReadAllBytes(const char* filePath, size_t& fileSize)\n{\n    FILE* file = fopen(filePath, \"rb\");\n\n    if (!file)\n        return std::make_unique<uint8_t[]>(0);\n\n    fseek(file, 0, SEEK_END);\n\n    fileSize = ftell(file);\n    fseek(file, 0, SEEK_SET);\n\n    auto data = std::make_unique<uint8_t[]>(fileSize);\n    fread(data.get(), 1, fileSize, file);\n\n    fclose(file);\n\n    return data;\n}\n\ninline bool strcmpIgnoreCase(const char* a, const char* b)\n{\n    for (size_t i = 0; i < strlen(a); i++)\n    {\n        if (a[i] != '\\0' && b[i] == '\\0')\n            return false;\n\n        if (a[i] == '\\0' && b[i] != '\\0')\n            return false;\n\n        auto c1 = std::tolower((uint8_t)a[i]);\n        auto c2 = std::tolower((uint8_t)b[i]);\n\n        if (c1 != c2)\n            return false;\n    }\n\n    return true;\n}\n\ninline bool strcmpWildcard(const char* str, const char* pattern)\n{\n    // Match if both strings passed all\n    // comparisons and reached the end.\n    if (*pattern == '\\0' && *str == '\\0')\n        return true;\n\n    // Check if any number of chars matches.\n    if (*pattern == '*')\n    {\n        // Skip duplicates.\n        while (*(pattern + 1) == '*')\n            pattern++;\n\n        return strcmpWildcard(str, pattern + 1) || (*str && strcmpWildcard(str + 1, pattern));\n    }\n\n    // Check if current char matches.\n    if (*pattern == '?' || *pattern == *str)\n        return strcmpWildcard(str + 1, pattern + 1);\n\n    return false;\n}\n\ninline std::u16string TransformUTF8ToWString(const std::string& str)\n{\n    std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter;\n    return converter.from_bytes(str);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/cache/pipeline_state_cache.h",
    "content": ""
  },
  {
    "path": "MarathonRecomp/gpu/cache/vertex_declaration_cache.h",
    "content": "g_vertexElements_03CB3EF6B1C43B8C,\ng_vertexElements_0EC0CD05EE1B1636,\ng_vertexElements_0FB4544424558E4C,\ng_vertexElements_28FD2057B9BD5D1B,\ng_vertexElements_2A6D72391BFFFA3C,\ng_vertexElements_5A22D93C543DF925,\ng_vertexElements_5A2395E29F93DA3C,\ng_vertexElements_6196BF64CB935CA5,\ng_vertexElements_6538EB0019C3A29A,\ng_vertexElements_6FAE71C7134074A4,\ng_vertexElements_75A4FC397A05F4CA,\ng_vertexElements_7F12180DC3A24B53,\ng_vertexElements_82A1B9D74331DB2A,\ng_vertexElements_84BACD816D86543C,\ng_vertexElements_A81F28FA43A9B511,\ng_vertexElements_B22B7B7B968141C6,\ng_vertexElements_B7BBCC93738C9DE4,\ng_vertexElements_C64D046063DE2F63,\ng_vertexElements_D452411D3FB80A0D,\ng_vertexElements_DEB308DCDDF979C7,\ng_vertexElements_E6B3B3D286909AB9,\ng_vertexElements_EFD61AD3C5332AAE,\ng_vertexElements_F10787EFFEEC0153,\ng_vertexElements_FFFDDC62D86892F1,\n"
  },
  {
    "path": "MarathonRecomp/gpu/cache/vertex_element_cache.h",
    "content": "static uint8_t g_vertexElements_03CB3EF6B1C43B8C[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x23,0xB9,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2A,0x23,0xB9,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x38,0x0,0x2C,0x23,0xA5,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x40,0x0,0x2C,0x23,0xA5,0x0,0x5,0x2,0x0,0x0,0x0,0x0,0x48,0x0,0x2C,0x23,0xA5,0x0,0x5,0x3,0x0,0x0,0x0,0x0,0x50,0x0,0x1A,0x23,0xA6,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x1A,0x23,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x64,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2C,0x82,0xA1,0x0,0x0,0x1,0x0,0x0,0x2,0x0,0x0,0x0,0x2C,0x83,0xA4,0x0,0x0,0x2,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_0EC0CD05EE1B1636[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2C,0x21,0x59,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x2C,0x21,0x59,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x14,0x0,0x18,0x28,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x23,0x90,0x0,0x3,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_0FB4544424558E4C[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_28FD2057B9BD5D1B[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2A,0x23,0xB9,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x23,0xB9,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x38,0x0,0x2C,0x23,0xA5,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x40,0x0,0x2C,0x23,0xA5,0x0,0x5,0x2,0x0,0x0,0x0,0x0,0x48,0x0,0x2C,0x23,0xA5,0x0,0x5,0x3,0x0,0x0,0x0,0x0,0x50,0x0,0x1A,0x23,0xA6,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x1A,0x23,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x64,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_2A6D72391BFFFA3C[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2C,0x23,0x5F,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x28,0x0,0x2C,0x23,0x5F,0x0,0x5,0x2,0x0,0x0,0x0,0x0,0x2C,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_5A22D93C543DF925[] = {0x0,0x0,0x0,0x0,0x0,0x2C,0x23,0xA5,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x18,0x28,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_5A2395E29F93DA3C[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_6196BF64CB935CA5[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x18,0x28,0x86,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_6538EB0019C3A29A[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2C,0x23,0x5F,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x20,0x0,0x2C,0x23,0x5F,0x0,0x5,0x2,0x0,0x0,0x0,0x0,0x24,0x0,0x2C,0x23,0x5F,0x0,0x5,0x3,0x0,0x0,0x0,0x0,0x28,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x2C,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_6FAE71C7134074A4[] = {0x0,0x0,0x0,0x0,0x0,0x2C,0x23,0xA5,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x18,0x28,0x86,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_75A4FC397A05F4CA[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x23,0xB9,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2A,0x23,0xB9,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x38,0x0,0x2C,0x23,0xA5,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x40,0x0,0x1A,0x23,0xA6,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_7F12180DC3A24B53[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x5,0x1,0x0,0x0,0x1,0x0,0xC,0x0,0x1A,0x22,0x86,0x0,0x5,0x2,0x0,0x0,0x1,0x0,0x10,0x0,0x2C,0x21,0x59,0x0,0x5,0x3,0x0,0x0,0x1,0x0,0x14,0x0,0x18,0x28,0x86,0x0,0xA,0x1,0x0,0x0,0x2,0x0,0x0,0x0,0x2C,0x82,0xA1,0x0,0x0,0x1,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_82A1B9D74331DB2A[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2C,0x23,0x5F,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x20,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x1A,0x20,0x86,0x0,0xA,0x1,0x0,0x0,0x0,0x0,0x28,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x2C,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_84BACD816D86543C[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x23,0xB9,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2A,0x23,0xB9,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x38,0x0,0x2C,0x23,0xA5,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x40,0x0,0x2C,0x23,0xA5,0x0,0x5,0x2,0x0,0x0,0x0,0x0,0x48,0x0,0x2C,0x23,0xA5,0x0,0x5,0x3,0x0,0x0,0x0,0x0,0x50,0x0,0x1A,0x23,0xA6,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x1A,0x23,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x64,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_A81F28FA43A9B511[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x18,0x28,0x86,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_B22B7B7B968141C6[] = {0x0,0x0,0x0,0x0,0x0,0x1A,0x23,0xA6,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x18,0x28,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_B7BBCC93738C9DE4[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_C64D046063DE2F63[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2C,0x23,0x5F,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x28,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_D452411D3FB80A0D[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_DEB308DCDDF979C7[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2C,0x23,0x5F,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x20,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x28,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_E6B3B3D286909AB9[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x5,0x1,0x0,0x0,0x1,0x0,0xC,0x0,0x1A,0x22,0x86,0x0,0x5,0x2,0x0,0x0,0x1,0x0,0x10,0x0,0x2C,0x83,0xA4,0x0,0x5,0x3,0x0,0x0,0x1,0x0,0x14,0x0,0x18,0x28,0x86,0x0,0xA,0x1,0x0,0x0,0x2,0x0,0x0,0x0,0x2C,0x82,0xA1,0x0,0x0,0x1,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_EFD61AD3C5332AAE[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2A,0x23,0xB9,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x24,0x0,0x2A,0x23,0xB9,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x38,0x0,0x2C,0x23,0xA5,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x40,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x44,0x0,0x1A,0x23,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x48,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_F10787EFFEEC0153[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2A,0x21,0x90,0x0,0x3,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x2A,0x21,0x90,0x0,0x6,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x21,0x90,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x18,0x0,0x2C,0x23,0x5F,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x1C,0x0,0x2C,0x23,0x5F,0x0,0x5,0x1,0x0,0x0,0x0,0x0,0x20,0x0,0x2C,0x23,0x5F,0x0,0x5,0x2,0x0,0x0,0x0,0x0,0x24,0x0,0x1A,0x20,0x86,0x0,0xA,0x0,0x0,0x0,0x0,0x0,0x28,0x0,0x1A,0x22,0x86,0x0,0x2,0x0,0x0,0x0,0x0,0x0,0x2C,0x0,0x1A,0x20,0x86,0x0,0x1,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\nstatic uint8_t g_vertexElements_FFFDDC62D86892F1[] = {0x0,0x0,0x0,0x0,0x0,0x2A,0x23,0xB9,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xC,0x0,0x2C,0x23,0xA5,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x14,0x0,0x2A,0x23,0xB9,0x0,0x3,0x0,0x0,0x0,0xFF,0x0,0x0,0xFF,0xFF,0xFF,0xFF,0x0,0x0,0x0,0x0,};\n"
  },
  {
    "path": "MarathonRecomp/gpu/imgui/imgui_common.cpp",
    "content": "#include \"imgui_common.h\"\n\nstatic std::vector<std::unique_ptr<ImGuiCallbackData>> g_callbackData;\nstatic uint32_t g_callbackDataIndex = 0;\n\nImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback)\n{\n    if (g_callbackDataIndex >= g_callbackData.size())\n        g_callbackData.emplace_back(std::make_unique<ImGuiCallbackData>());\n\n    auto& callbackData = g_callbackData[g_callbackDataIndex];\n    ++g_callbackDataIndex;\n\n    ImGui::GetBackgroundDrawList()->AddCallback(reinterpret_cast<ImDrawCallback>(callback), callbackData.get());\n\n    return callbackData.get();\n}\n\nvoid ResetImGuiCallbacks()\n{\n    g_callbackDataIndex = 0;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/imgui/imgui_common.h",
    "content": "#pragma once\n\n#define IMGUI_SHADER_MODIFIER_NONE                     0\n#define IMGUI_SHADER_MODIFIER_TEXT_SKEW                1\n#define IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE  2\n#define IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE    3\n#define IMGUI_SHADER_MODIFIER_GRAYSCALE                4\n#define IMGUI_SHADER_MODIFIER_TITLE_BEVEL              5\n#define IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL           6\n#define IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL          7\n#define IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT         8\n\n#if defined(__cplusplus) && !defined(__air__)\n\nenum class ImGuiCallback : int32_t\n{\n    SetGradient = -1,\n    SetShaderModifier = -2,\n    SetOrigin = -3,\n    SetScale = -4,\n    SetMarqueeFade = -5,\n    SetOutline = -6,\n    SetProceduralOrigin = -7,\n    // -8 is ImDrawCallback_ResetRenderState, don't use!\n    SetAdditive = -9\n};\n\nunion ImGuiCallbackData\n{\n    struct\n    {\n        float boundsMin[2];\n        float boundsMax[2];\n        uint32_t gradientTopLeft;\n        uint32_t gradientTopRight;\n        uint32_t gradientBottomRight;\n        uint32_t gradientBottomLeft;\n    } setGradient;\n\n    struct\n    {\n        uint32_t shaderModifier;\n    } setShaderModifier;\n\n    struct\n    {\n        float origin[2];\n    } setOrigin;\n\n    struct\n    {\n        float scale[2];\n    } setScale;\n\n    struct\n    {\n        float boundsMin[2];\n        float boundsMax[2];\n    } setMarqueeFade;\n\n    struct\n    {\n        float outline;\n    } setOutline;\n\n    struct\n    {\n        float proceduralOrigin[2];\n    } setProceduralOrigin;\n\n    struct\n    {\n        bool enabled;\n    } setAdditive;\n};\n\nextern ImGuiCallbackData* AddImGuiCallback(ImGuiCallback callback);\n\nextern void ResetImGuiCallbacks();\n\n#endif\n"
  },
  {
    "path": "MarathonRecomp/gpu/imgui/imgui_font_builder.cpp",
    "content": "#include \"imgui_font_builder.h\"\n\n#include <msdf-atlas-gen/msdf-atlas-gen.h>\n\n// Taken directly from msdf-atlas-gen, modified to support custom rectangles.\nstruct TightAtlasPacker {\n\n    TightAtlasPacker() :\n        width(-1), height(-1),\n        spacing(0),\n        dimensionsConstraint(msdf_atlas::DimensionsConstraint::POWER_OF_TWO_SQUARE),\n        scale(-1),\n        minScale(1),\n        unitRange(0),\n        pxRange(0),\n        miterLimit(0),\n        pxAlignOriginX(false), pxAlignOriginY(false),\n        scaleMaximizationTolerance(.001)\n    {\n    }\n\n    int pack(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount) {\n        double initialScale = scale > 0 ? scale : minScale;\n        if (initialScale > 0) {\n            if (int remaining = tryPack(glyphs, count, customRects, customRectCount, dimensionsConstraint, width, height, initialScale))\n                return remaining;\n        }\n        else if (width < 0 || height < 0)\n            return -1;\n        if (scale <= 0)\n            scale = packAndScale(glyphs, count, customRects, customRectCount);\n        if (scale <= 0)\n            return -1;\n        return 0;\n    }\n\n    int width, height;\n    int spacing;\n    msdf_atlas::DimensionsConstraint dimensionsConstraint;\n    double scale;\n    double minScale;\n    msdfgen::Range unitRange;\n    msdfgen::Range pxRange;\n    double miterLimit;\n    bool pxAlignOriginX, pxAlignOriginY;\n    msdf_atlas::Padding innerUnitPadding, outerUnitPadding;\n    msdf_atlas::Padding innerPxPadding, outerPxPadding;\n    double scaleMaximizationTolerance;\n\n    int tryPack(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount, msdf_atlas::DimensionsConstraint dimensionsConstraint, int& width, int& height, double scale) const {\n        // Wrap glyphs into boxes\n        std::vector<msdf_atlas::Rectangle> rectangles;\n        std::vector<msdf_atlas::GlyphGeometry*> rectangleGlyphs;\n        rectangles.reserve(count + customRectCount);\n        rectangleGlyphs.reserve(count);\n        msdf_atlas::GlyphGeometry::GlyphAttributes attribs = { };\n        attribs.scale = scale;\n        attribs.range = { unitRange.lower + pxRange.lower / scale, unitRange.upper + pxRange.upper / scale };\n        attribs.innerPadding = innerUnitPadding + innerPxPadding / scale;\n        attribs.outerPadding = outerUnitPadding + outerPxPadding / scale;\n        attribs.miterLimit = miterLimit;\n        attribs.pxAlignOriginX = pxAlignOriginX;\n        attribs.pxAlignOriginY = pxAlignOriginY;\n        for (msdf_atlas::GlyphGeometry* glyph = glyphs, *end = glyphs + count; glyph < end; ++glyph) {\n            if (!glyph->isWhitespace()) {\n                msdf_atlas::Rectangle rect = { };\n                glyph->wrapBox(attribs);\n                glyph->getBoxSize(rect.w, rect.h);\n                if (rect.w > 0 && rect.h > 0) {\n                    rectangles.push_back(rect);\n                    rectangleGlyphs.push_back(glyph);\n                }\n            }\n        }\n        rectangles.insert(rectangles.end(), customRects, customRects + customRectCount);\n        // No non-zero size boxes?\n        if (rectangles.empty()) {\n            if (width < 0 || height < 0)\n                width = 0, height = 0;\n            return 0;\n        }\n        // Box rectangle packing\n        if (width < 0 || height < 0) {\n            std::pair<int, int> dimensions = std::make_pair(width, height);\n            switch (dimensionsConstraint) {\n            case msdf_atlas::DimensionsConstraint::POWER_OF_TWO_SQUARE:\n                dimensions = msdf_atlas::packRectangles<msdf_atlas::SquarePowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), spacing);\n                break;\n            case msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE:\n                dimensions = msdf_atlas::packRectangles<msdf_atlas::PowerOfTwoSizeSelector>(rectangles.data(), rectangles.size(), spacing);\n                break;\n            case msdf_atlas::DimensionsConstraint::MULTIPLE_OF_FOUR_SQUARE:\n                dimensions = msdf_atlas::packRectangles<msdf_atlas::SquareSizeSelector<4> >(rectangles.data(), rectangles.size(), spacing);\n                break;\n            case msdf_atlas::DimensionsConstraint::EVEN_SQUARE:\n                dimensions = msdf_atlas::packRectangles<msdf_atlas::SquareSizeSelector<2> >(rectangles.data(), rectangles.size(), spacing);\n                break;\n            case msdf_atlas::DimensionsConstraint::SQUARE:\n            default:\n                dimensions = msdf_atlas::packRectangles<msdf_atlas::SquareSizeSelector<> >(rectangles.data(), rectangles.size(), spacing);\n                break;\n            }\n            if (!(dimensions.first > 0 && dimensions.second > 0))\n                return -1;\n            width = dimensions.first, height = dimensions.second;\n        }\n        else {\n            if (int result = packRectangles(rectangles.data(), rectangles.size(), width, height, spacing))\n                return result;\n        }\n\n        // Set glyph box placement\n        for (size_t i = 0; i < rectangleGlyphs.size(); ++i)\n            rectangleGlyphs[i]->placeBox(rectangles[i].x, height - (rectangles[i].y + rectangles[i].h));     \n\n        for (int i = 0; i < customRectCount; ++i) {\n            customRects[i].x = rectangles[rectangleGlyphs.size() + i].x;\n            customRects[i].y = height - (rectangles[rectangleGlyphs.size() + i].y + rectangles[rectangleGlyphs.size() + i].h);\n        }\n\n        return 0;\n    }\n\n    double packAndScale(msdf_atlas::GlyphGeometry* glyphs, int count, msdf_atlas::Rectangle* customRects, int customRectCount) const {\n        bool lastResult = false;\n        int w = width, h = height;\n#define TRY_PACK(scale) (lastResult = !tryPack(glyphs, count, customRects, customRectCount, msdf_atlas::DimensionsConstraint(), w, h, (scale)))\n        double minScale = 1, maxScale = 1;\n        if (TRY_PACK(1)) {\n            while (maxScale < 1e+32 && ((maxScale = 2 * minScale), TRY_PACK(maxScale)))\n                minScale = maxScale;\n        }\n        else {\n            while (minScale > 1e-32 && ((minScale = .5 * maxScale), !TRY_PACK(minScale)))\n                maxScale = minScale;\n        }\n        if (minScale == maxScale)\n            return 0;\n        while (minScale / maxScale < 1 - scaleMaximizationTolerance) {\n            double midScale = .5 * (minScale + maxScale);\n            if (TRY_PACK(midScale))\n                minScale = midScale;\n            else\n                maxScale = midScale;\n        }\n        if (!lastResult)\n            TRY_PACK(minScale);\n        return minScale;\n    }\n};\n\nextern void ImFontAtlasBuildInit(ImFontAtlas* atlas);\nextern void ImFontAtlasBuildFinish(ImFontAtlas* atlas);\n\nstatic bool FontBuilder_Build(ImFontAtlas* atlas)\n{\n    ImFontAtlasBuildInit(atlas);\n\n    auto freeType = msdfgen::initializeFreetype();\n\n    std::vector<msdf_atlas::GlyphGeometry> glyphs;\n    std::vector<std::pair<size_t, size_t>> ranges;\n    std::vector<msdf_atlas::Rectangle> customRects;\n\n    for (auto& config : atlas->ConfigData)\n    {\n        msdf_atlas::Charset charset;\n        const ImWchar* glyphRanges = config.GlyphRanges;\n        while (*glyphRanges != NULL)\n        {\n            for (ImWchar i = glyphRanges[0]; i <= glyphRanges[1]; i++)\n                charset.add(i);\n\n            glyphRanges += 2;\n        }\n\n        size_t index = glyphs.size();\n\n        auto font = msdfgen::loadFontData(freeType, reinterpret_cast<const msdfgen::byte*>(config.FontData), config.FontDataSize);\n\n        msdf_atlas::FontGeometry fontGeometry(&glyphs);\n        fontGeometry.loadCharset(font, config.SizePixels, charset);\n\n        auto& metrics = fontGeometry.getMetrics();\n        config.DstFont->FontSize = config.SizePixels;\n        config.DstFont->ConfigData = &config;\n        config.DstFont->ConfigDataCount = 1;\n        config.DstFont->ContainerAtlas = atlas;\n        config.DstFont->Ascent = metrics.ascenderY;\n        config.DstFont->Descent = metrics.descenderY;\n\n        msdfgen::destroyFont(font);\n\n        ranges.emplace_back(index, glyphs.size() - index);\n    }\n\n    for (auto& glyph : glyphs)\n        glyph.edgeColoring(&msdfgen::edgeColoringByDistance, 3.0, 0);\n\n    for (auto& customRect : atlas->CustomRects)\n        customRects.emplace_back(0, 0, int(customRect.Width), int(customRect.Height));\n\n    TightAtlasPacker packer;\n    packer.spacing = 1;\n    packer.dimensionsConstraint = msdf_atlas::DimensionsConstraint::POWER_OF_TWO_RECTANGLE;\n    packer.miterLimit = 1.0;\n    packer.pxRange = 8.0;\n    packer.pack(glyphs.data(), glyphs.size(), customRects.data(), customRects.size());\n\n    for (size_t i = 0; i < customRects.size(); i++)\n    {\n        auto& srcRect = customRects[i];\n        auto& dstRect = atlas->CustomRects[i];\n        dstRect.X = srcRect.x;\n        dstRect.Y = srcRect.y;\n    }\n\n    msdf_atlas::ImmediateAtlasGenerator<float, 3, &msdf_atlas::msdfGenerator, msdf_atlas::BitmapAtlasStorage<uint8_t, 3>> generator(packer.width, packer.height);\n    generator.generate(glyphs.data(), glyphs.size());\n\n    for (size_t i = 0; i < atlas->ConfigData.size(); i++)\n    {\n        auto& config = atlas->ConfigData[i];\n\n        double spaceAdvance = 0.0;\n        double spaceMultiplier = 1.0;\n\n        if (strstr(config.Name, \"FOT-Rodin\") != nullptr)\n        {\n            spaceMultiplier = 1.75;\n        }\n        else if (strstr(config.Name, \"FOT-NewRodin\") != nullptr)\n        {\n            spaceMultiplier = 2.5;\n        }\n\n        auto& [index, count] = ranges[i];\n        for (size_t j = 0; j < count; j++)\n        {\n            auto& glyph = glyphs[index + j];\n            double x0, y0, x1, y1, u0, v0, u1, v1;\n            glyph.getQuadPlaneBounds(x0, y0, x1, y1);\n            glyph.getQuadAtlasBounds(u0, v0, u1, v1);\n\n            double advance = glyph.getAdvance();\n            if (glyph.getCodepoint() == ' ')\n            {\n                advance *= spaceMultiplier;\n                spaceAdvance = advance;\n            }\n\n            config.DstFont->AddGlyph(\n                &config,\n                glyph.getCodepoint(),\n                x0, \n                -y1 + config.DstFont->Ascent, \n                x1,\n                -y0 + config.DstFont->Ascent,\n                u0 / packer.width, \n                v1 / packer.height, \n                u1 / packer.width, \n                v0 / packer.height, \n                advance);\n        }\n        \n        // Used as a zero-width helper for automatic line breaks.\n        // This is useful for languages like Japanese to separate 'words'\n        // so that they don't get split mid-kana by the automatic splitter.\n        config.DstFont->AddGlyph(\n            &config,\n            0x200B,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f);\n\n        // A duplicate of the normal width space character.\n        // Overrides the unicode Four-Per-Em Space character.\n        // This can be used to add visual spacers that are ignored\n        // by the automatic line splitting logic.\n        config.DstFont->AddGlyph(\n            &config,\n            0x2005,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            0.0f,\n            spaceAdvance);\n\n        config.DstFont->BuildLookupTable();\n    }\n\n    atlas->TexPixelsRGBA32 = (unsigned int*)IM_ALLOC(packer.width * packer.height * 4);\n    atlas->TexWidth = packer.width;\n    atlas->TexHeight = packer.height;\n    atlas->TexUvScale = { 1.0f / packer.width, 1.0f / packer.height };\n\n    auto bitmapRef = (msdfgen::BitmapConstRef<uint8_t, 3>)generator.atlasStorage();\n    for (int y = 0; y < packer.height; y++)\n    {\n        for (int x = 0; x < packer.width; x++)\n        {\n            auto* srcPixels = bitmapRef(x, y);\n            auto* dstPixels = (uint8_t*)&atlas->TexPixelsRGBA32[y * packer.width + x];\n            dstPixels[0] = srcPixels[0];\n            dstPixels[1] = srcPixels[1];\n            dstPixels[2] = srcPixels[2];\n            dstPixels[3] = 0xFF;\n        }\n    }\n\n    msdfgen::deinitializeFreetype(freeType);\n\n    ImFontAtlasBuildFinish(atlas);\n\n    return true;\n}\n\nImFontBuilderIO g_fontBuilderIO = { FontBuilder_Build };\n"
  },
  {
    "path": "MarathonRecomp/gpu/imgui/imgui_font_builder.h",
    "content": "#pragma once\n\nextern ImFontBuilderIO g_fontBuilderIO;\n"
  },
  {
    "path": "MarathonRecomp/gpu/imgui/imgui_snapshot.cpp",
    "content": "#include \"imgui_snapshot.h\"\n\n#include <locale/locale.h>\n#include <res/font/im_font_atlas.bin.h>\n#include <user/config.h>\n#include <decompressor.h>\n#include <kernel/xdbf.h>\n#include <app.h>\n\ntemplate<typename T1, typename T2>\nvoid ImFontAtlasSnapshot::SnapPointer(size_t offset, const T1& value, const T2& ptr, size_t count)\n{\n    if (ptr != nullptr && count != 0)\n    {\n        if (!objects.contains(ptr))\n        {\n            constexpr size_t ALIGN = alignof(std::remove_pointer_t<T2>);\n            constexpr size_t SIZE = sizeof(std::remove_pointer_t<T2>);\n\n            size_t ptrOffset = (data.size() + ALIGN - 1) & ~(ALIGN - 1);\n            data.resize(ptrOffset + SIZE * count);\n            memcpy(&data[ptrOffset], ptr, SIZE * count);\n\n            for (size_t i = 0; i < count; i++)\n            {\n                size_t curPtrOffset = ptrOffset + SIZE * i;\n                objects[&ptr[i]] = curPtrOffset;\n                Traverse(curPtrOffset, ptr[i]);\n            }\n        }\n\n        size_t fieldOffset = offset + (reinterpret_cast<ptrdiff_t>(&ptr) - reinterpret_cast<ptrdiff_t>(&value));\n        *reinterpret_cast<size_t*>(&data[fieldOffset]) = objects[ptr];\n        offsets.push_back(fieldOffset);\n    }\n}\n\ntemplate<typename T>\nvoid ImFontAtlasSnapshot::Traverse(size_t offset, const T& value)\n{\n    if constexpr (std::is_pointer_v<T>)\n    {\n        SnapPointer(offset, value, value, 1);\n    }\n    else if constexpr (std::is_same_v<T, ImFontAtlas>)\n    {\n        SnapPointer(offset, value, value.ConfigData.Data, value.ConfigData.Size);\n        SnapPointer(offset, value, value.CustomRects.Data, value.CustomRects.Size);\n        SnapPointer(offset, value, value.Fonts.Data, value.Fonts.Size);\n    }\n    else if constexpr (std::is_same_v<T, ImFont>)\n    {\n        SnapPointer(offset, value, value.IndexAdvanceX.Data, value.IndexAdvanceX.Size);\n        SnapPointer(offset, value, value.IndexLookup.Data, value.IndexLookup.Size);\n        SnapPointer(offset, value, value.Glyphs.Data, value.Glyphs.Size);\n        SnapPointer(offset, value, value.FallbackGlyph, 1);\n        SnapPointer(offset, value, value.ContainerAtlas, 1);\n        SnapPointer(offset, value, value.ConfigData, value.ConfigDataCount);\n    }\n    else if constexpr (std::is_same_v<T, ImFontAtlasCustomRect>)\n    {\n        SnapPointer(offset, value, value.Font, 1);\n    }  \n    else if constexpr (std::is_same_v<T, ImFontConfig>)\n    {\n        SnapPointer(offset, value, value.GlyphRanges, value.GlyphRanges != nullptr ? wcslen(reinterpret_cast<const wchar_t*>(value.GlyphRanges)) + 1 : 0);\n        SnapPointer(offset, value, value.DstFont, 1);\n    }\n}\n\ntemplate<typename T>\nsize_t ImFontAtlasSnapshot::Snap(const T& value)\n{\n    size_t offset = (data.size() + alignof(T) - 1) & ~(alignof(T) - 1);\n    data.resize(offset + sizeof(T));\n    memcpy(&data[offset], &value, sizeof(T));\n    objects[&value] = offset;\n    Traverse(offset, value);\n    return offset;\n}\n\nstruct ImFontAtlasSnapshotHeader\n{\n    uint32_t imguiVersion;\n    uint32_t dataOffset;\n    uint32_t offsetCount;\n    uint32_t offsetsOffset;\n};\n\nvoid ImFontAtlasSnapshot::Snap()\n{\n    data.resize(sizeof(ImFontAtlasSnapshotHeader));\n    size_t dataOffset = Snap(*ImGui::GetIO().Fonts);\n\n    size_t offsetsOffset = data.size();\n    std::sort(offsets.begin(), offsets.end());\n\n    data.insert(data.end(), \n        reinterpret_cast<uint8_t*>(offsets.data()),\n        reinterpret_cast<uint8_t*>(offsets.data() + offsets.size()));\n\n    auto header = reinterpret_cast<ImFontAtlasSnapshotHeader*>(data.data());\n    header->imguiVersion = IMGUI_VERSION_NUM;\n    header->dataOffset = dataOffset;\n    header->offsetCount = offsets.size();\n    header->offsetsOffset = offsetsOffset;\n}\n\nstatic std::unique_ptr<uint8_t[]> g_imFontAtlas;\n\nImFontAtlas* ImFontAtlasSnapshot::Load()\n{\n    g_imFontAtlas = decompressZstd(g_im_font_atlas, g_im_font_atlas_uncompressed_size);\n\n    auto header = reinterpret_cast<ImFontAtlasSnapshotHeader*>(g_imFontAtlas.get());\n    assert(header->imguiVersion == IMGUI_VERSION_NUM && \"ImGui version mismatch, the font atlas needs to be regenerated!\");\n\n    auto offsetTable = reinterpret_cast<uint32_t*>(g_imFontAtlas.get() + header->offsetsOffset);\n    for (size_t i = 0; i < header->offsetCount; i++)\n    {\n        *reinterpret_cast<size_t*>(g_imFontAtlas.get() + (*offsetTable)) += reinterpret_cast<size_t>(g_imFontAtlas.get());\n        ++offsetTable;\n    }\n\n    return reinterpret_cast<ImFontAtlas*>(g_imFontAtlas.get() + header->dataOffset);\n}\n\n\nstatic void GetGlyphs(std::set<ImWchar>& glyphs, const std::string_view& value)\n{\n    const char* cur = value.data();\n    while (cur < value.data() + value.size())\n    {\n        unsigned int c;\n        cur += ImTextCharFromUtf8(&c, cur, value.data() + value.size());\n        glyphs.emplace(c);\n    }\n}\n\nstatic std::vector<ImWchar> g_glyphRanges;\n\nvoid ImFontAtlasSnapshot::GenerateGlyphRanges()\n{\n    std::vector<std::string_view> localeStrings;\n\n    for (auto& config : g_configDefinitions)\n        config->GetLocaleStrings(localeStrings);\n\n    std::set<ImWchar> glyphs;\n\n    for (size_t i = 0x20; i <= 0xFF; i++)\n        glyphs.emplace(i);\n\n    for (auto& localeString : localeStrings)\n        GetGlyphs(glyphs, localeString);\n\n    for (auto& [name, locale] : g_locale)\n    {\n        for (auto& [language, value] : locale)\n            GetGlyphs(glyphs, value);\n    }\n\n    for (size_t i = XDBF_LANGUAGE_ENGLISH; i <= XDBF_LANGUAGE_ITALIAN; i++)\n    {\n        auto achievements = g_xdbfWrapper.GetAchievements(static_cast<EXDBFLanguage>(i));\n        for (auto& achievement : achievements)\n        {\n            GetGlyphs(glyphs, achievement.Name);\n            GetGlyphs(glyphs, achievement.UnlockedDesc);\n            GetGlyphs(glyphs, achievement.LockedDesc);\n        }\n    }\n\n    for (auto glyph : glyphs)\n    {\n        if (g_glyphRanges.empty() || (g_glyphRanges.back() + 1) != glyph)\n        {\n            g_glyphRanges.push_back(glyph);\n            g_glyphRanges.push_back(glyph);\n        }\n        else\n        {\n            g_glyphRanges.back() = glyph;\n        }\n    }\n\n    g_glyphRanges.push_back(0);\n}\n\nImFont* ImFontAtlasSnapshot::GetFont(const char* name)\n{\n    auto fontAtlas = ImGui::GetIO().Fonts;\n    for (auto& configData : fontAtlas->ConfigData)\n    {\n        if (strstr(configData.Name, name) != nullptr)\n        {\n            assert(configData.DstFont != nullptr);\n            return configData.DstFont;\n        }\n    }\n\n#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT\n    assert(false && \"Unable to locate equivalent font in the atlas file.\");\n#endif\n\n    return fontAtlas->AddFontFromFileTTF(name, 24.0f, nullptr, g_glyphRanges.data());\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/imgui/imgui_snapshot.h",
    "content": "#pragma once\n\n// Undefine this to generate a font atlas file in working directory.\n// You also need to do this if you are testing localization, as only\n// characters in the locale get added to the atlas.\n#define ENABLE_IM_FONT_ATLAS_SNAPSHOT\n\nstruct ImFontAtlasSnapshot\n{\n    std::vector<uint8_t> data;\n    ankerl::unordered_dense::map<const void*, size_t> objects;\n    std::vector<uint32_t> offsets;\n\n    template<typename T1, typename T2>\n    void SnapPointer(size_t offset, const T1& value, const T2& ptr, size_t count);\n\n    template<typename T>\n    void Traverse(size_t offset, const T& value);\n\n    template<typename T>\n    size_t Snap(const T& value);\n\n    void Snap();\n\n    static ImFontAtlas* Load();\n\n    static void GenerateGlyphRanges();\n\n    // When ENABLE_IM_FONT_ATLAS_SNAPSHOT is undefined, this creates the font runtime instead. \n    static ImFont* GetFont(const char* name);\n};\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/.gitignore",
    "content": "*.hlsl.*.h\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/blend_color_alpha_ps.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define g_SrcAlpha_DestAlpha vk::RawBufferLoad<float4>(g_PushConstants.PixelShaderConstants + 2400, 0x10)\n#define s0_Texture2DDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 0)\n#define s0_SamplerDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 192)\n\n#else\n\ncbuffer PixelShaderConstants : register(b1, space4)\n{\n    float4 g_SrcAlpha_DestAlpha : packoffset(c150);\n};\n\ncbuffer SharedConstants : register(b2, space4)\n{\n    uint s0_Texture2DDescriptorIndex : packoffset(c0.x);\n    uint s0_SamplerDescriptorIndex : packoffset(c12.x);\n    DEFINE_SHARED_CONSTANTS();\n};\n\n#endif\n\nfloat4 shaderMain(\n    in float4 iPos : SV_Position,\n    in float4 iTexCoord0 : TEXCOORD0) : SV_Target0\n{\n    Texture2D<float4> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex];\n    SamplerState samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex];\n    \n    float4 color = texture.Sample(samplerState, iTexCoord0.xy);\n    \n    if (any(or(iTexCoord0.xy < 0.0, iTexCoord0.xy > 1.0)))\n        color = float4(0.0, 0.0, 0.0, 1.0);\n    \n    color.rgb *= color.a * g_SrcAlpha_DestAlpha.x;\n    color.a = g_SrcAlpha_DestAlpha.y + (1.0 - color.a) * g_SrcAlpha_DestAlpha.x;\n    \n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/conditional_survey_ps.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n[earlydepthstencil]\nfloat4 shaderMain() : SV_Target\n{\n    atomicFetchAddUint(g_ConditionalSurveyBuffer, g_conditionalSurveyIndex, 1);\n    return float4(0.0, 0.0, 0.0, 0.0);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/copy_color_ps.hlsl",
    "content": "#include \"copy_common.hlsli\"\n\nTexture2D<float4> g_Texture2DDescriptorHeap[] : register(t0, space0);\n\nfloat4 shaderMain(in float4 position : SV_Position) : SV_Target\n{\n    return g_Texture2DDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].Load(int3(position.xy, 0));\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/copy_common.hlsli",
    "content": "#pragma once\n\nstruct PushConstants\n{\n    uint ResourceDescriptorIndex;\n};\n\n[[vk::push_constant]] ConstantBuffer<PushConstants> g_PushConstants : register(b3, space4);\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/copy_depth_ps.hlsl",
    "content": "#include \"copy_common.hlsli\"\n\nTexture2D<float> g_Texture2DDescriptorHeap[] : register(t0, space0);\n\nfloat shaderMain(in float4 position : SV_Position) : SV_Depth\n{\n    return g_Texture2DDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].Load(int3(position.xy, 0));\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/copy_vs.hlsl",
    "content": "void shaderMain(in uint vertexId : SV_VertexID, out float4 position : SV_Position, out float2 texCoord : TEXCOORD)\n{\n    texCoord = float2((vertexId << 1) & 2, vertexId & 2);\n    position = float4(texCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/csd_filter_ps.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define s0_Texture2DDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 0)\n#define s0_SamplerDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 192)\n\n#else\n\ncbuffer SharedConstants : register(b2, space4)\n{\n    uint s0_Texture2DDescriptorIndex : packoffset(c0.x);\n    uint s0_SamplerDescriptorIndex : packoffset(c12.x);\n\tDEFINE_SHARED_CONSTANTS();\n};\n\n#endif\n\nfloat4 shaderMain(\n\tin float4 iPosition : SV_Position,\n\tin float4 iTexCoord0 : TEXCOORD0,\n\tin float4 iTexCoord1 : TEXCOORD1) : SV_Target\n{\n    Texture2D<float4> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex];\n    SamplerState samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex];\n    \n    uint2 dimensions;\n    texture.GetDimensions(dimensions.x, dimensions.y);\n    \n    // https://www.shadertoy.com/view/csX3RH\n    float2 uvTexspace = iTexCoord1.xy * dimensions;\n    float2 seam = floor(uvTexspace + 0.5);\n    uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam;\n    uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5);\n    float2 texCoord = uvTexspace / dimensions;\n    \n    float4 color = texture.Sample(samplerState, texCoord);\n    color *= iTexCoord0;\n    \n    // The game enables alpha test for CSD, but the alpha threshold doesn't seem to be assigned anywhere? Weird.\n    clip(color.a - g_AlphaThreshold);\n    \n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/csd_no_tex_vs.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define g_ViewportSize vk::RawBufferLoad<float4>(g_PushConstants.VertexShaderConstants + 2880, 0x10)\n#define g_Z vk::RawBufferLoad<float4>(g_PushConstants.VertexShaderConstants + 3936, 0x10)\n\n#else\n\ncbuffer VertexShaderConstants : register(b0, space4)\n{\n    float4 g_ViewportSize : packoffset(c180);\n    float4 g_Z : packoffset(c246);\n};\n\ncbuffer SharedConstants : register(b2, space4)\n{\n\tDEFINE_SHARED_CONSTANTS();\n};\n\n#endif\n\nvoid shaderMain(\n\t[[vk::location(0)]] in float4 iPosition0 : POSITION0,\n\t[[vk::location(8)]] in float4 iColor0 : COLOR0,\n\tout float4 oPos : SV_Position,\n\tout float4 oTexCoord0 : TEXCOORD0,\n\tout float4 oTexCoord1 : TEXCOORD1,\n\tout float4 oTexCoord2 : TEXCOORD2,\n\tout float4 oTexCoord3 : TEXCOORD3,\n\tout float4 oTexCoord4 : TEXCOORD4,\n\tout float4 oTexCoord5 : TEXCOORD5,\n\tout float4 oTexCoord6 : TEXCOORD6,\n\tout float4 oTexCoord7 : TEXCOORD7,\n\tout float4 oTexCoord8 : TEXCOORD8,\n\tout float4 oTexCoord9 : TEXCOORD9,\n\tout float4 oTexCoord10 : TEXCOORD10,\n\tout float4 oTexCoord11 : TEXCOORD11,\n\tout float4 oTexCoord12 : TEXCOORD12,\n\tout float4 oTexCoord13 : TEXCOORD13,\n\tout float4 oTexCoord14 : TEXCOORD14,\n\tout float4 oTexCoord15 : TEXCOORD15,\n\tout float4 oColor0 : COLOR0,\n\tout float4 oColor1 : COLOR1)\n{\n    oPos.xy = iPosition0.xy * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0);\n    oPos.z = g_Z.x;\n    oPos.w = 1.0;\n    oTexCoord0 = iColor0.wxyz;\n    oTexCoord1 = 0.0;\n    oTexCoord2 = 0.0;\n    oTexCoord3 = 0.0;\n    oTexCoord4 = 0.0;\n    oTexCoord5 = 0.0;\n    oTexCoord6 = 0.0;\n    oTexCoord7 = 0.0;\n    oTexCoord8 = 0.0;\n    oTexCoord9 = 0.0;\n    oTexCoord10 = 0.0;\n    oTexCoord11 = 0.0;\n    oTexCoord12 = 0.0;\n    oTexCoord13 = 0.0;\n    oTexCoord14 = 0.0;\n    oTexCoord15 = 0.0;\n    oColor0 = 0.0;\n    oColor1 = 0.0;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/csd_vs.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define g_ViewportSize vk::RawBufferLoad<float4>(g_PushConstants.VertexShaderConstants + 2880, 0x10)\n#define g_Z vk::RawBufferLoad<float4>(g_PushConstants.VertexShaderConstants + 3936, 0x10)\n\n#else\n\ncbuffer VertexShaderConstants : register(b0, space4)\n{\n    float4 g_ViewportSize : packoffset(c180);\n    float4 g_Z : packoffset(c246);\n};\n\ncbuffer SharedConstants : register(b2, space4)\n{\n\tDEFINE_SHARED_CONSTANTS();\n};\n\n#endif\n\nvoid shaderMain(\n\t[[vk::location(0)]] in float4 iPosition0 : POSITION0,\n\t[[vk::location(8)]] in float4 iColor0 : COLOR0,\n\t[[vk::location(4)]] in float4 iTexCoord0 : TEXCOORD0,\n\tout float4 oPos : SV_Position,\n\tout float4 oTexCoord0 : TEXCOORD0,\n\tout float4 oTexCoord1 : TEXCOORD1,\n\tout float4 oTexCoord2 : TEXCOORD2,\n\tout float4 oTexCoord3 : TEXCOORD3,\n\tout float4 oTexCoord4 : TEXCOORD4,\n\tout float4 oTexCoord5 : TEXCOORD5,\n\tout float4 oTexCoord6 : TEXCOORD6,\n\tout float4 oTexCoord7 : TEXCOORD7,\n\tout float4 oTexCoord8 : TEXCOORD8,\n\tout float4 oTexCoord9 : TEXCOORD9,\n\tout float4 oTexCoord10 : TEXCOORD10,\n\tout float4 oTexCoord11 : TEXCOORD11,\n\tout float4 oTexCoord12 : TEXCOORD12,\n\tout float4 oTexCoord13 : TEXCOORD13,\n\tout float4 oTexCoord14 : TEXCOORD14,\n\tout float4 oTexCoord15 : TEXCOORD15,\n\tout float4 oColor0 : COLOR0,\n\tout float4 oColor1 : COLOR1)\n{    \n    oPos.xy = iPosition0.xy * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0);\n    oPos.z = g_Z.x;\n    oPos.w = 1.0;\n    oTexCoord0 = iColor0.wxyz;\n    oTexCoord1.xy = iTexCoord0.xy;\n    oTexCoord1.zw = 0.0;\n    oTexCoord2 = 0.0;\n    oTexCoord3 = 0.0;\n    oTexCoord4 = 0.0;\n    oTexCoord5 = 0.0;\n    oTexCoord6 = 0.0;\n    oTexCoord7 = 0.0;\n    oTexCoord8 = 0.0;\n    oTexCoord9 = 0.0;\n    oTexCoord10 = 0.0;\n    oTexCoord11 = 0.0;\n    oTexCoord12 = 0.0;\n    oTexCoord13 = 0.0;\n    oTexCoord14 = 0.0;\n    oTexCoord15 = 0.0;\n    oColor0 = 0.0;\n    oColor1 = 0.0;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/enhanced_burnout_blur_ps.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define s0_Texture2DDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 0)\n#define s0_SamplerDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 192)\n\n#else\n\ncbuffer SharedConstants : register(b2, space4)\n{\n    uint s0_Texture2DDescriptorIndex : packoffset(c0.x);\n    uint s0_SamplerDescriptorIndex : packoffset(c12.x);\n    DEFINE_SHARED_CONSTANTS();\n};\n\n#endif\n\nfloat4 shaderMain(\n    in float4 oPos : SV_Position,\n    in float2 oTexCoord0 : TEXCOORD0,\n    in float2 oVelocity : TEXCOORD1,\n    in float2 oVelScale : TEXCOORD2) : SV_Target\n{\n    Texture2D<float4> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex];\n    SamplerState samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex];\n\n    float velocityMag = sqrt(dot(oVelocity, oVelocity));\n    float blurAmount = saturate(velocityMag - 0.25);\n\n    if (blurAmount == 0.0)\n        return texture.SampleLevel(samplerState, oTexCoord0, 0);\n\n    blurAmount = min(blurAmount * blurAmount, 0.25);\n\n    float blurStrength = blurAmount * velocityMag;\n    int g_SampleCount = clamp((int)(blurStrength * 256.0), 4, 64);\n    float2 scaledVelStep = oVelScale / (float)g_SampleCount;\n\n    float4 result = float4(0.0, 0.0, 0.0, 0.0);\n\n    for (int i = 0; i < g_SampleCount; i++)\n    {\n        float2 offset = blurAmount * scaledVelStep * (float)i;\n        float2 samplePos = oTexCoord0 + offset;\n\n        result += texture.SampleLevel(samplerState, samplePos, 0);\n    }\n\n    return result * (1.0 / (float)g_SampleCount);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/enhanced_burnout_blur_vs.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define g_Velocity vk::RawBufferLoad<float4>(g_PushConstants.VertexShaderConstants + 3360, 0x10)\n\n#else\n\ncbuffer VertexShaderConstants : register(b1, space4)\n{\n    float4 g_Velocity : packoffset(c210);\n};\n\n#endif\n\nvoid shaderMain(\n    [[vk::location(0)]] in float4 iPosition0 : POSITION0,\n    [[vk::location(13)]] in float2 iTexCoord0 : TEXCOORD0,\n    out float4 oPos : SV_Position,\n    out float2 oTexCoord0 : TEXCOORD0,\n    out float2 oVelocity : TEXCOORD1,\n    out float2 oVelScale : TEXCOORD2)\n{\n    oPos = iPosition0;\n    oTexCoord0 = iTexCoord0;\n\n    float2 centeredUV;\n    centeredUV.x = iTexCoord0.y * 2.0 - 1.0;\n    centeredUV.y = iTexCoord0.x * 2.0 - 1.0;\n\n    oVelocity.x = -g_Velocity.x - centeredUV.y;\n    oVelocity.y = centeredUV.x - g_Velocity.y;\n\n    float2 scaledVec = oVelocity * g_Velocity.w;\n    oVelScale.x = scaledVec.x * 0.00002;\n    oVelScale.y = -scaledVec.y * 0.00001;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/gamma_correction_ps.hlsl",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define g_Gamma vk::RawBufferLoad<float>(g_PushConstants.SharedConstants + 0)\n#define g_TextureDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 4)\n\n#define g_ViewportOffset vk::RawBufferLoad<int2>(g_PushConstants.SharedConstants + 8)\n#define g_ViewportSize vk::RawBufferLoad<int2>(g_PushConstants.SharedConstants + 16)\n\n#else\n\ncbuffer SharedConstants : register(b2, space4)\n{\n    float g_Gamma;\n    uint g_TextureDescriptorIndex;\n    int2 g_ViewportOffset;\n    int2 g_ViewportSize;\n};\n\n#endif\n\nfloat4 shaderMain(in float4 position : SV_Position) : SV_Target\n{\n    Texture2D<float4> texture = g_Texture2DDescriptorHeap[g_TextureDescriptorIndex];\n    \n    int2 movedPosition = int2(position.xy) - g_ViewportOffset;\n    bool boxed = any(movedPosition < 0) || any(movedPosition >= g_ViewportSize);\n    if (boxed) movedPosition = 0;\n    \n    float4 color = boxed ? 0.0 : texture.Load(int3(movedPosition, 0));\n    color.rgb = pow(color.rgb, g_Gamma);\n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/gaussian_blur.hlsli",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef __spirv__\n\n#define g_ViewportSize vk::RawBufferLoad<float4>(g_PushConstants.PixelShaderConstants + 384, 0x10)\n#define g_offsets(INDEX) select((INDEX) < 74, vk::RawBufferLoad<float4>(g_PushConstants.PixelShaderConstants + (150 + min(INDEX, 73)) * 16, 0x10), 0.0)\n#define g_weights vk::RawBufferLoad<float4>(g_PushConstants.PixelShaderConstants + 2656, 0x10)\n\n#define s0_Texture2DDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 0)\n#define s0_SamplerDescriptorIndex vk::RawBufferLoad<uint>(g_PushConstants.SharedConstants + 192)\n\n#else\n\ncbuffer PixelShaderConstants : register(b1, space4)\n{\n    float4 g_ViewportSize : packoffset(c24);\n    float4 g_offsets[2] : packoffset(c150);\n#define g_offsets(INDEX) select((INDEX) < 74, g_offsets[min(INDEX, 73)], 0.0)\n    float4 g_weights : packoffset(c166);\n};\n\ncbuffer SharedConstants : register(b2, space4)\n{\n    uint s0_Texture2DDescriptorIndex : packoffset(c0.x);\n    uint s0_SamplerDescriptorIndex : packoffset(c12.x);\n\tDEFINE_SHARED_CONSTANTS();\n};\n\n#endif\n\n#ifdef __INTELLISENSE__\n#define KERNEL_SIZE 5\n#endif\n\n#define PI 3.14159265358979323846\n\nfloat ComputeWeight(float x)\n{\n    float std = 0.952;\n    return exp(-(x * x) / (2.0 * std * std)) / (std * sqrt(2.0 * PI));\n}\n\nfloat4 shaderMain(in float4 iPosition : SV_Position, in float4 iTexCoord0 : TEXCOORD0) : SV_Target\n{\n    Texture2D<float4> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex];\n    SamplerState samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex];\n    \n    float scale;\n    if ((g_ViewportSize.x * g_ViewportSize.w) >= (16.0 / 9.0))\n        scale = g_ViewportSize.y / 360.0;\n    else\n        scale = g_ViewportSize.x / 640.0;\n    \n    float2 offsets[3];\n    offsets[0] = g_offsets(0).xy * scale;\n    offsets[1] = g_offsets(0).zw * scale;\n    offsets[2] = g_offsets(1).xy * scale;\n    \n    float4 color = 0.0;\n    float weightSum = 0.0;\n    \n    [unroll]\n    for (int i = 0; i < KERNEL_SIZE; i++)\n    {\n        float step = i / float(KERNEL_SIZE - 1);\n        float scaled = step * 2;\n        float2 offset = lerp(offsets[int(scaled)], offsets[min(int(scaled) + 1, 2)], frac(scaled));\n        float offsetScale = 1.0 / 0.75;\n        float weight = ComputeWeight(lerp(-offsetScale, offsetScale, step));\n        color += texture.SampleLevel(samplerState, iTexCoord0.xy + offset, 0) * weight;\n        weightSum += weight;\n    }\n    \n    return color / weightSum;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/gaussian_blur_3x3.hlsl",
    "content": "#define KERNEL_SIZE 3\n#include \"gaussian_blur.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/gaussian_blur_5x5.hlsl",
    "content": "#define KERNEL_SIZE 5\n#include \"gaussian_blur.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/gaussian_blur_7x7.hlsl",
    "content": "#define KERNEL_SIZE 7\n#include \"gaussian_blur.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/gaussian_blur_9x9.hlsl",
    "content": "#define KERNEL_SIZE 9\n#include \"gaussian_blur.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/imgui_common.hlsli",
    "content": "#pragma once\n\n#include \"../../imgui/imgui_common.h\"\n\nstruct PushConstants\n{\n    float2 BoundsMin;\n    float2 BoundsMax;\n    uint GradientTopLeft;\n    uint GradientTopRight;\n    uint GradientBottomRight;\n    uint GradientBottomLeft;\n    uint ShaderModifier;\n    uint Texture2DDescriptorIndex;\n    float2 DisplaySize;\n    float2 InverseDisplaySize;\n    float2 Origin;\n    float2 Scale;\n    float2 ProceduralOrigin;\n    float Outline;\n};\n\nTexture2D<float4> g_Texture2DDescriptorHeap[] : register(t0, space0);\nSamplerState g_SamplerDescriptorHeap[] : register(s0, space1);\n[[vk::push_constant]] ConstantBuffer<PushConstants> g_PushConstants : register(b0, space2);\n\nstruct Interpolators\n{\n    float4 Position : SV_Position;\n    float2 UV : TEXCOORD;\n    float4 Color : COLOR;\n};\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/imgui_ps.hlsl",
    "content": "#include \"imgui_common.hlsli\"\n\nfloat4 DecodeColor(uint color)\n{\n    return float4(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF, (color >> 24) & 0xFF) / 255.0;\n}\n\nfloat4 SamplePoint(int2 position)\n{\n    return 1.0;\n}\n\nfloat4 SampleLinear(float2 uvTexspace)\n{    \n    int2 integerPart = floor(uvTexspace);\n    float2 fracPart = frac(uvTexspace);\n    \n    float4 topLeft = SamplePoint(integerPart + float2(0, 0));\n    float4 topRight = SamplePoint(integerPart + float2(1, 0));\n    float4 bottomLeft = SamplePoint(integerPart + float2(0, 1));\n    float4 bottomRight = SamplePoint(integerPart + float2(1, 1));\n    \n    float4 top = lerp(topLeft, topRight, fracPart.x);\n    float4 bottom = lerp(bottomLeft, bottomRight, fracPart.x);\n\n    return lerp(top, bottom, fracPart.y);\n}\n\nfloat4 PixelAntialiasing(float2 uvTexspace)\n{    \n    if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0))\n        uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0;\n    else\n        uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0;\n\n    float2 seam = floor(uvTexspace + 0.5);\n    uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam;\n    uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5);\n    \n    return SampleLinear(uvTexspace - 0.5);\n}\n\nfloat median(float r, float g, float b)\n{\n    return max(min(r, g), min(max(r, g), b));\n}\n\nfloat4 SampleSdfFont(float4 color, Texture2D<float4> texture, float2 uv, float2 screenTexSize)\n{\n    float4 textureColor = texture.Sample(g_SamplerDescriptorHeap[0], uv);\n    \n    uint width, height;\n    texture.GetDimensions(width, height);\n            \n    float pxRange = 8.0;\n    float2 unitRange = pxRange / float2(width, height);\n    float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0);\n            \n    float sd = median(textureColor.r, textureColor.g, textureColor.b) - 0.5;\n    float screenPxDistance = screenPxRange * (sd + g_PushConstants.Outline / (pxRange * 1.5));\n            \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TITLE_BEVEL)\n    {\n        float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.01)).xy;\n        float3 rimColor = float3(1, 0.8, 0.29);\n        float3 shadowColor = float3(0.84, 0.57, 0);\n\n        float cosTheta = dot(normal, normalize(float2(1, 1)));\n        float3 gradient = lerp(color.rgb, cosTheta >= 0.0 ? rimColor : shadowColor, abs(cosTheta));\n        color.rgb = lerp(gradient, color.rgb, pow(saturate(sd + 0.77), 32.0));\n    }\n    else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL)\n    {\n        float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.25)).xy;\n        float cosTheta = dot(normal, normalize(float2(1, 1)));\n        float gradient = 1.0 + cosTheta * 0.5;\n        color.rgb = saturate(color.rgb * gradient);\n    }\n    else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW)\n    {\n        float2 normal = normalize(float3(ddx(sd), ddy(sd), 0.5)).xy;\n        float cosTheta = dot(normal, normalize(float2(1, 1)));\n        float gradient = saturate(1.0 + cosTheta);\n        color.rgb = lerp(color.rgb * gradient, color.rgb, pow(saturate(sd + 0.77), 32.0));\n    }\n\n    color.a *= saturate(screenPxDistance + 0.5);\n    color.a *= textureColor.a;\n    \n    return color;\n}\n\nfloat4 shaderMain(in Interpolators interpolators) : SV_Target\n{\n    float4 color = interpolators.Color;\n    color *= PixelAntialiasing(interpolators.Position.xy - g_PushConstants.ProceduralOrigin);\n    \n    if (g_PushConstants.Texture2DDescriptorIndex != 0)\n    {\n        Texture2D<float4> texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF];\n        \n        if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0)\n        {\n            if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT)\n            {\n                float scale;\n                float invScale;\n                \n                if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0))\n                {\n                    scale = g_PushConstants.InverseDisplaySize.y * 720.0;\n                    invScale = g_PushConstants.DisplaySize.y / 720.0;\n                }\n                else\n                {\n                    scale = g_PushConstants.InverseDisplaySize.x * 960.0;\n                    invScale = g_PushConstants.DisplaySize.x / 960.0;\n                }\n                \n                float2 lowQualityPosition = (interpolators.Position.xy - 0.5) * scale;                \n                float2 fracPart = frac(lowQualityPosition);\n                \n                float2 uvStep = fwidth(interpolators.UV) * invScale;\n                float2 lowQualityUV = interpolators.UV - fracPart * uvStep;\n                float2 screenTexSize = 1.0 / uvStep;\n                \n                float4 topLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, 0), screenTexSize);\n                float4 topRight = SampleSdfFont(color, texture, lowQualityUV + float2(uvStep.x, 0), screenTexSize);\n                float4 bottomLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, uvStep.y), screenTexSize);\n                float4 bottomRight = SampleSdfFont(color, texture, lowQualityUV + uvStep.xy, screenTexSize);\n                \n                float4 top = lerp(topLeft, topRight, fracPart.x);\n                float4 bottom = lerp(bottomLeft, bottomRight, fracPart.x);\n                \n                color = lerp(top, bottom, fracPart.y);\n            }\n            else\n            {\n                color = SampleSdfFont(color, texture, interpolators.UV, 1.0 / fwidth(interpolators.UV));\n            }\n        }\n        else\n        {\n            color *= texture.Sample(g_SamplerDescriptorHeap[0], interpolators.UV);\n        }\n    }\n    \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE)\n    {\n        float minAlpha = saturate((interpolators.Position.x - g_PushConstants.BoundsMin.x) / g_PushConstants.Scale.x);\n        float maxAlpha = saturate((g_PushConstants.BoundsMax.x - interpolators.Position.x) / g_PushConstants.Scale.y);\n        \n        color.a *= minAlpha;\n        color.a *= maxAlpha;\n    }\n    else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE)\n    {\n        float minAlpha = saturate((interpolators.Position.y - g_PushConstants.BoundsMin.y) / g_PushConstants.Scale.x);\n        float maxAlpha = saturate((g_PushConstants.BoundsMax.y - interpolators.Position.y) / g_PushConstants.Scale.y);\n        \n        color.a *= minAlpha;\n        color.a *= maxAlpha;\n    }\n    else if (any(g_PushConstants.BoundsMin != g_PushConstants.BoundsMax))\n    {\n        float2 factor = saturate((interpolators.Position.xy - g_PushConstants.BoundsMin) / (g_PushConstants.BoundsMax - g_PushConstants.BoundsMin));\n        \n        if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL)\n        {\n            float bevelSize = 0.9;\n\n            float shadow = saturate((factor.x - bevelSize) / (1.0 - bevelSize));\n            shadow = max(shadow, saturate((factor.y - bevelSize) / (1.0 - bevelSize)));\n\n            float rim = saturate((1.0 - factor.x - bevelSize) / (1.0 - bevelSize));\n            rim = max(rim, saturate((1.0 - factor.y - bevelSize) / (1.0 - bevelSize)));\n\n            float3 rimColor = float3(1, 0.8, 0.29);\n            float3 shadowColor = float3(0.84, 0.57, 0);\n\n            color.rgb = lerp(color.rgb, rimColor, smoothstep(0.0, 1.0, rim));\n            color.rgb = lerp(color.rgb, shadowColor, smoothstep(0.0, 1.0, shadow));\n        }\n        else\n        {\n            float4 top = lerp(DecodeColor(g_PushConstants.GradientTopLeft), DecodeColor(g_PushConstants.GradientTopRight), smoothstep(0.0, 1.0, factor.x));\n            float4 bottom = lerp(DecodeColor(g_PushConstants.GradientBottomLeft), DecodeColor(g_PushConstants.GradientBottomRight), smoothstep(0.0, 1.0, factor.x));\n            color *= lerp(top, bottom, smoothstep(0.0, 1.0, factor.y));\n        }\n    }\n        \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_GRAYSCALE)\n        color.rgb = dot(color.rgb, float3(0.2126, 0.7152, 0.0722));\n    \n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/imgui_vs.hlsl",
    "content": "#include \"imgui_common.hlsli\"\n\nvoid shaderMain(in float2 position : POSITION, in float2 uv : TEXCOORD, in float4 color : COLOR, out Interpolators interpolators)\n{    \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW)\n    {\n        if (position.y < g_PushConstants.Origin.y)\n            position.x += g_PushConstants.Scale.x;\n    }\n    else if (g_PushConstants.ShaderModifier != IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE && \n        g_PushConstants.ShaderModifier != IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE)\n    {\n        position.xy = g_PushConstants.Origin + (position.xy - g_PushConstants.Origin) * g_PushConstants.Scale;\n    }\n    \n    interpolators.Position = float4(position.xy * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);\n    interpolators.UV = uv;\n    interpolators.Color = color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_color.hlsli",
    "content": "#pragma once\n\n#include \"copy_common.hlsli\"\n\nTexture2DMS<float4, SAMPLE_COUNT> g_Texture2DMSDescriptorHeap[] : register(t0, space0);\n\nfloat4 shaderMain(in float4 position : SV_Position) : SV_Target\n{\n    float4 result = g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].Load(int2(position.xy), 0);\n    \n    [unroll] for (int i = 1; i < SAMPLE_COUNT; i++)\n        result += g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].Load(int2(position.xy), i);\n\n    return result / SAMPLE_COUNT;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_color_2x.hlsl",
    "content": "#define SAMPLE_COUNT 2\n#include \"resolve_msaa_color.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_color_4x.hlsl",
    "content": "#define SAMPLE_COUNT 4\n#include \"resolve_msaa_color.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_color_8x.hlsl",
    "content": "#define SAMPLE_COUNT 8\n#include \"resolve_msaa_color.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_depth.hlsli",
    "content": "#pragma once\n\n#include \"copy_common.hlsli\"\n\nTexture2DMS<float, SAMPLE_COUNT> g_Texture2DMSDescriptorHeap[] : register(t0, space0);\n\nfloat shaderMain(in float4 position : SV_Position) : SV_Depth\n{\n    float result = g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].Load(int2(position.xy), 0);\n    \n    [unroll] for (int i = 1; i < SAMPLE_COUNT; i++)\n        result = min(result, g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].Load(int2(position.xy), i));\n\n    return result;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_depth_2x.hlsl",
    "content": "#define SAMPLE_COUNT 2\n#include \"resolve_msaa_depth.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_depth_4x.hlsl",
    "content": "#define SAMPLE_COUNT 4\n#include \"resolve_msaa_depth.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/hlsl/resolve_msaa_depth_8x.hlsl",
    "content": "#define SAMPLE_COUNT 8\n#include \"resolve_msaa_depth.hlsli\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/.gitignore",
    "content": "*.ir\n*.metallib\n*.metal.*.c\n*.metal.*.h"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/blend_color_alpha_ps.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define g_SrcAlpha_DestAlpha (*(reinterpret_cast<device float4*>(g_PushConstants.PixelShaderConstants + 2400)))\n#define s0_Texture2DDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 0)))\n#define s0_SamplerDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 192)))\n\nstruct Interpolators\n{\n    float4 iTexCoord0 [[user(TEXCOORD0)]];\n};\n\n[[fragment]]\nfloat4 shaderMain(float4 iPos [[position]],\n                  Interpolators input [[stage_in]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    texture2d<float> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex].tex;\n    sampler samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp;\n    \n    float4 color = texture.sample(samplerState, input.iTexCoord0.xy);\n    \n    if (any(input.iTexCoord0.xy < 0.0 || input.iTexCoord0.xy > 1.0))\n        color = float4(0.0, 0.0, 0.0, 1.0);\n    \n    color.rgb *= color.a * g_SrcAlpha_DestAlpha.x;\n    color.a = g_SrcAlpha_DestAlpha.y + (1.0 - color.a) * g_SrcAlpha_DestAlpha.x;\n    \n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/conditional_survey_ps.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n[[fragment]]\n[[early_fragment_tests]]\nfloat4 shaderMain(device AtomicUintBuffer* g_ConditionalSurveyBuffer [[buffer(4)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    atomicFetchAddUint(g_ConditionalSurveyBuffer, g_conditionalSurveyIndex, 1);\n    return float4(0.0, 0.0, 0.0, 0.0);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/copy_color_ps.metal",
    "content": "#include \"copy_common.metali\"\n\nstruct Texture2DDescriptorHeap\n{\n    texture2d<float> tex;\n};\n\n[[fragment]]\nfloat4 shaderMain(float4 position [[position]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    return g_Texture2DDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/copy_common.metali",
    "content": "#pragma once\n#include <metal_stdlib>\n\nusing namespace metal;\n\nstruct PushConstants\n{\n    uint ResourceDescriptorIndex;\n};\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/copy_depth_ps.metal",
    "content": "#include \"copy_common.metali\"\n\nstruct Texture2DDescriptorHeap\n{\n    texture2d<float> tex;\n};\n\nstruct PixelShaderOutput\n{\n    float oDepth [[depth(any)]];\n};\n\n[[fragment]]\nPixelShaderOutput shaderMain(float4 position [[position]],\n                             constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                             constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    PixelShaderOutput output = PixelShaderOutput{};\n\n    output.oDepth = g_Texture2DDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0).x;\n\n    return output;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/copy_vs.metal",
    "content": "struct Interpolators\n{\n    float4 position [[position]];\n    float2 texCoord [[user(TEXCOORD)]];\n};\n\n[[vertex]]\nInterpolators shaderMain(uint vertexId [[vertex_id]])\n{\n    Interpolators interpolators;\n\n    interpolators.texCoord = float2((vertexId << 1) & 2, vertexId & 2);\n    interpolators.position = float4(interpolators.texCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);\n\n    return interpolators;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/csd_filter_ps.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define s0_Texture2DDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 0)))\n#define s0_SamplerDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 192)))\n\nstruct Interpolators\n{\n    float4 iPosition [[position]];\n    float4 iTexCoord0 [[user(TEXCOORD0)]];\n    float4 iTexCoord1 [[user(TEXCOORD1)]];\n};\n\n[[fragment]]\nfloat4 shaderMain(Interpolators input [[stage_in]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    texture2d<float> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex].tex;\n    sampler samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp;\n\n    uint2 dimensions = getTexture2DDimensions(texture);\n\n    // https://www.shadertoy.com/view/csX3RH\n    float2 uvTexspace = input.iTexCoord1.xy * float2(dimensions);\n    float2 seam = floor(uvTexspace + 0.5);\n    uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam;\n    uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5);\n    float2 texCoord = uvTexspace / float2(dimensions);\n    \n    float4 color = texture.sample(samplerState, texCoord);\n    color *= input.iTexCoord0;\n    \n    // The game enables alpha test for CSD, but the alpha threshold doesn't seem to be assigned anywhere? Weird.\n    clip(color.a - g_AlphaThreshold);\n    \n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/csd_no_tex_vs.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define g_ViewportSize (*(reinterpret_cast<device float4*>(g_PushConstants.VertexShaderConstants + 2880)))\n#define g_Z (*(reinterpret_cast<device float4*>(g_PushConstants.VertexShaderConstants + 3936)))\n\nstruct VertexShaderInput\n{\n    float4 iPosition0 [[attribute(0)]];\n    float4 iColor0 [[attribute(8)]];\n};\n\nstruct Interpolators\n{\n    float4 oPos [[position]];\n    float4 oTexCoord0 [[user(TEXCOORD0)]];\n    float4 oTexCoord1 [[user(TEXCOORD1)]];\n    float4 oTexCoord2 [[user(TEXCOORD2)]];\n    float4 oTexCoord3 [[user(TEXCOORD3)]];\n    float4 oTexCoord4 [[user(TEXCOORD4)]];\n    float4 oTexCoord5 [[user(TEXCOORD5)]];\n    float4 oTexCoord6 [[user(TEXCOORD6)]];\n    float4 oTexCoord7 [[user(TEXCOORD7)]];\n    float4 oTexCoord8 [[user(TEXCOORD8)]];\n    float4 oTexCoord9 [[user(TEXCOORD9)]];\n    float4 oTexCoord10 [[user(TEXCOORD10)]];\n    float4 oTexCoord11 [[user(TEXCOORD11)]];\n    float4 oTexCoord12 [[user(TEXCOORD12)]];\n    float4 oTexCoord13 [[user(TEXCOORD13)]];\n    float4 oTexCoord14 [[user(TEXCOORD14)]];\n    float4 oTexCoord15 [[user(TEXCOORD15)]];\n    float4 oColor0 [[user(COLOR0)]];\n    float4 oColor1 [[user(COLOR1)]];\n};\n\n[[vertex]]\nInterpolators shaderMain(VertexShaderInput input [[stage_in]],\n                         constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    Interpolators output;\n\n    output.oPos.xy = input.iPosition0.xy * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0);\n    output.oPos.z = g_Z.x;\n    output.oPos.w = 1.0;\n    output.oTexCoord0 = input.iColor0.wxyz;\n    output.oTexCoord1 = 0.0;\n    output.oTexCoord2 = 0.0;\n    output.oTexCoord3 = 0.0;\n    output.oTexCoord4 = 0.0;\n    output.oTexCoord5 = 0.0;\n    output.oTexCoord6 = 0.0;\n    output.oTexCoord7 = 0.0;\n    output.oTexCoord8 = 0.0;\n    output.oTexCoord9 = 0.0;\n    output.oTexCoord10 = 0.0;\n    output.oTexCoord11 = 0.0;\n    output.oTexCoord12 = 0.0;\n    output.oTexCoord13 = 0.0;\n    output.oTexCoord14 = 0.0;\n    output.oTexCoord15 = 0.0;\n    output.oColor0 = 0.0;\n    output.oColor1 = 0.0;\n\n    return output;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/csd_vs.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define g_ViewportSize (*(reinterpret_cast<device float4*>(g_PushConstants.VertexShaderConstants + 2880)))\n#define g_Z (*(reinterpret_cast<device float4*>(g_PushConstants.VertexShaderConstants + 3936)))\n\nstruct VertexShaderInput\n{\n    float4 iPosition0 [[attribute(0)]];\n    float4 iColor0 [[attribute(8)]];\n    float4 iTexCoord0 [[attribute(4)]];\n};\n\nstruct Interpolators\n{\n    float4 oPos [[position]];\n    float4 oTexCoord0 [[user(TEXCOORD0)]];\n    float4 oTexCoord1 [[user(TEXCOORD1)]];\n    float4 oTexCoord2 [[user(TEXCOORD2)]];\n    float4 oTexCoord3 [[user(TEXCOORD3)]];\n    float4 oTexCoord4 [[user(TEXCOORD4)]];\n    float4 oTexCoord5 [[user(TEXCOORD5)]];\n    float4 oTexCoord6 [[user(TEXCOORD6)]];\n    float4 oTexCoord7 [[user(TEXCOORD7)]];\n    float4 oTexCoord8 [[user(TEXCOORD8)]];\n    float4 oTexCoord9 [[user(TEXCOORD9)]];\n    float4 oTexCoord10 [[user(TEXCOORD10)]];\n    float4 oTexCoord11 [[user(TEXCOORD11)]];\n    float4 oTexCoord12 [[user(TEXCOORD12)]];\n    float4 oTexCoord13 [[user(TEXCOORD13)]];\n    float4 oTexCoord14 [[user(TEXCOORD14)]];\n    float4 oTexCoord15 [[user(TEXCOORD15)]];\n    float4 oColor0 [[user(COLOR0)]];\n    float4 oColor1 [[user(COLOR1)]];\n};\n\n[[vertex]]\nInterpolators shaderMain(VertexShaderInput input [[stage_in]],\n                         constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    Interpolators output;\n\n    output.oPos.xy = input.iPosition0.xy * g_ViewportSize.zw * float2(2.0, -2.0) + float2(-1.0, 1.0);\n    output.oPos.z = g_Z.x;\n    output.oPos.w = 1.0;\n    output.oTexCoord0 = input.iColor0.wxyz;\n    output.oTexCoord1.xy = input.iTexCoord0.xy;\n    output.oTexCoord1.zw = 0.0;\n    output.oTexCoord2 = 0.0;\n    output.oTexCoord3 = 0.0;\n    output.oTexCoord4 = 0.0;\n    output.oTexCoord5 = 0.0;\n    output.oTexCoord6 = 0.0;\n    output.oTexCoord7 = 0.0;\n    output.oTexCoord8 = 0.0;\n    output.oTexCoord9 = 0.0;\n    output.oTexCoord10 = 0.0;\n    output.oTexCoord11 = 0.0;\n    output.oTexCoord12 = 0.0;\n    output.oTexCoord13 = 0.0;\n    output.oTexCoord14 = 0.0;\n    output.oTexCoord15 = 0.0;\n    output.oColor0 = 0.0;\n    output.oColor1 = 0.0;\n\n    return output;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/enhanced_burnout_blur_ps.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define s0_TextureDescriptorIndex (*(reinterpret_cast<device int*>(g_PushConstants.SharedConstants + 0)))\n#define s0_SamplerDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 192)))\n\nstruct Interpolators\n{\n    float4 oPos [[position]];\n    float2 oTexCoord0 [[user(TEXCOORD0)]];\n    float2 oVelocity [[user(TEXCOORD1)]];\n    float2 oVelScale [[user(TEXCOORD2)]];\n};\n\n[[fragment]]\nfloat4 shaderMain(Interpolators input [[stage_in]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    texture2d<float> texture = g_Texture2DDescriptorHeap[s0_TextureDescriptorIndex].tex;\n    sampler samp = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp;\n\n    float velocityMag = sqrt(dot(input.oVelocity, input.oVelocity));\n    float blurAmount = saturate(velocityMag - 0.25);\n\n    if (blurAmount == 0.0)\n        return texture.sample(samp, input.oTexCoord0, level(0));\n\n    blurAmount = min(blurAmount * blurAmount, 0.25);\n\n    float blurStrength = blurAmount * velocityMag;\n    int g_SampleCount = clamp((int)(blurStrength * 256.0), 4, 64);\n    float2 scaledVelStep = input.oVelScale / (float)g_SampleCount;\n\n    float4 result = float4(0.0);\n\n    for (int i = 0; i < g_SampleCount; i++)\n    {\n        float2 offset = blurAmount * scaledVelStep * (float)i;\n        float2 samplePos = input.oTexCoord0 + offset;\n\n        result += texture.sample(samp, samplePos, level(0));\n    }\n\n    return result * (1.0 / (float)g_SampleCount);\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/enhanced_burnout_blur_vs.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n// #define g_SampleCount (*(reinterpret_cast<device int*>(g_PushConstants.VertexShaderConstants + 0)))\n#define g_Velocity (*(reinterpret_cast<device float4*>(g_PushConstants.VertexShaderConstants + 3360)))\n\nstruct VertexShaderInput\n{\n    float4 iPosition0 [[attribute(0)]];\n    float2 iTexCoord0 [[attribute(13)]];\n};\n\nstruct Interpolators\n{\n    float4 oPos [[position]];\n    float2 oTexCoord0 [[user(TEXCOORD0)]];\n    float2 oVelocity [[user(TEXCOORD1)]];\n    float2 oVelScale [[user(TEXCOORD2)]];\n};\n\n[[vertex]]\nInterpolators shaderMain(VertexShaderInput input [[stage_in]],\n                         constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    Interpolators output;\n\n    output.oPos = input.iPosition0;\n    output.oTexCoord0 = input.iTexCoord0;\n\n    float2 centeredUV;\n    centeredUV.x = input.iTexCoord0.y * 2.0 - 1.0;\n    centeredUV.y = input.iTexCoord0.x * 2.0 - 1.0;\n\n    output.oVelocity.x = -g_Velocity.x - centeredUV.y;\n    output.oVelocity.y = centeredUV.x - g_Velocity.y;\n\n    float2 scaledVec = output.oVelocity * g_Velocity.w;\n    output.oVelScale.x = scaledVec.x * 0.00002;\n    output.oVelScale.y = -scaledVec.y * 0.00001;\n\n    return output;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/gamma_correction_ps.metal",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define g_Gamma (*(reinterpret_cast<device float*>(g_PushConstants.SharedConstants + 0)))\n#define g_TextureDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 4)))\n\n#define g_ViewportOffset (*(reinterpret_cast<device int2*>(g_PushConstants.SharedConstants + 8)))\n#define g_ViewportSize (*(reinterpret_cast<device int2*>(g_PushConstants.SharedConstants + 16)))\n\n[[fragment]]\nfloat4 shaderMain(float4 position [[position]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    texture2d<float> texture = g_Texture2DDescriptorHeap[g_TextureDescriptorIndex].tex;\n    \n    int2 movedPosition = int2(position.xy) - g_ViewportOffset;\n    bool boxed = any(movedPosition < 0) || any(movedPosition >= g_ViewportSize);\n    if (boxed) movedPosition = 0;\n    \n    float4 color = boxed ? 0.0 : texture.read(uint2(movedPosition), 0);\n    color.rgb = pow(color.rgb, g_Gamma);\n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/gaussian_blur.metali",
    "content": "#include \"../../../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#define g_ViewportSize (*(reinterpret_cast<device float4*>(g_PushConstants.PixelShaderConstants + 384)))\n#define g_offsets(INDEX) selectWrapper((INDEX) < 74,(*(reinterpret_cast<device float4*>(g_PushConstants.PixelShaderConstants + (150 + min(INDEX, 73)) * 16))), 0.0)\n#define g_weights (*(reinterpret_cast<device float4*>(g_PushConstants.PixelShaderConstants + 2656)))\n\n#define s0_Texture2DDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 0)))\n#define s0_SamplerDescriptorIndex (*(reinterpret_cast<device uint*>(g_PushConstants.SharedConstants + 192)))\n\n#ifdef __INTELLISENSE__\n#define KERNEL_SIZE 5\n#endif\n\n#define PI 3.14159265358979323846\n\nfloat ComputeWeight(float x)\n{\n    float std = 0.952;\n    return exp(-(x * x) / (2.0 * std * std)) / (std * sqrt(2.0 * PI));\n}\n\nstruct Interpolators\n{\n    float4 iTexCoord0 [[user(TEXCOORD0)]];\n};\n\n[[fragment]]\nfloat4 shaderMain(float4 iPosition [[position]],\n                  Interpolators input [[stage_in]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(3)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    texture2d<float> texture = g_Texture2DDescriptorHeap[s0_Texture2DDescriptorIndex].tex;\n    sampler samplerState = g_SamplerDescriptorHeap[s0_SamplerDescriptorIndex].samp;\n    \n    float scale;\n    if ((g_ViewportSize.x * g_ViewportSize.w) >= (16.0 / 9.0))\n        scale = g_ViewportSize.y / 360.0;\n    else\n        scale = g_ViewportSize.x / 640.0;\n    \n    float2 offsets[3];\n    offsets[0] = g_offsets(0).xy * scale;\n    offsets[1] = g_offsets(0).zw * scale;\n    offsets[2] = g_offsets(1).xy * scale;\n    \n    float4 color = 0.0;\n    float weightSum = 0.0;\n\n    for (int i = 0; i < KERNEL_SIZE; i++)\n    {\n        float step = i / float(KERNEL_SIZE - 1);\n        float scaled = step * 2;\n        float2 offset = mix(offsets[int(scaled)], offsets[min(int(scaled) + 1, 2)], frac(scaled));\n        float offsetScale = 1.0 / 0.75;\n        float weight = ComputeWeight(mix(-offsetScale, offsetScale, step));\n        color += texture.sample(samplerState, input.iTexCoord0.xy + offset, level(0)) * weight;\n        weightSum += weight;\n    }\n    \n    return color / weightSum;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/gaussian_blur_3x3.metal",
    "content": "#define KERNEL_SIZE 3\n#include \"gaussian_blur.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/gaussian_blur_5x5.metal",
    "content": "#define KERNEL_SIZE 5\n#include \"gaussian_blur.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/gaussian_blur_7x7.metal",
    "content": "#define KERNEL_SIZE 7\n#include \"gaussian_blur.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/gaussian_blur_9x9.metal",
    "content": "#define KERNEL_SIZE 9\n#include \"gaussian_blur.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/imgui_common.metali",
    "content": "#pragma once\n#include <metal_stdlib>\n\nusing namespace metal;\n\n#include \"../../imgui/imgui_common.h\"\n\nstruct PushConstants\n{\n    float2 BoundsMin;\n    float2 BoundsMax;\n    uint GradientTopLeft;\n    uint GradientTopRight;\n    uint GradientBottomRight;\n    uint GradientBottomLeft;\n    uint ShaderModifier;\n    uint Texture2DDescriptorIndex;\n    float2 DisplaySize;\n    float2 InverseDisplaySize;\n    float2 Origin;\n    float2 Scale;\n    float2 ProceduralOrigin;\n    float Outline;\n};\n\nstruct Interpolators\n{\n    float4 Position [[position]];\n    float2 UV;\n    float4 Color;\n};\n\nstruct Texture2DDescriptorHeap\n{\n    texture2d<float> tex;\n};\n\nstruct SamplerDescriptorHeap\n{\n    sampler samp;\n};"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/imgui_ps.metal",
    "content": "#include \"imgui_common.metali\"\n\nfloat4 DecodeColor(uint color)\n{\n    return float4(color & 0xFF, (color >> 8) & 0xFF, (color >> 16) & 0xFF, (color >> 24) & 0xFF) / 255.0;\n}\n\nfloat4 SamplePoint(int2 position, constant PushConstants& g_PushConstants)\n{\n    return 1.0;\n}\n\nfloat4 SampleLinear(float2 uvTexspace, constant PushConstants& g_PushConstants)\n{    \n    int2 integerPart = int2(floor(uvTexspace));\n    float2 fracPart = fract(uvTexspace);\n    \n    float4 topLeft = SamplePoint(integerPart + int2(0, 0), g_PushConstants);\n    float4 topRight = SamplePoint(integerPart + int2(1, 0), g_PushConstants);\n    float4 bottomLeft = SamplePoint(integerPart + int2(0, 1), g_PushConstants);\n    float4 bottomRight = SamplePoint(integerPart + int2(1, 1), g_PushConstants);\n    \n    float4 top = mix(topLeft, topRight, fracPart.x);\n    float4 bottom = mix(bottomLeft, bottomRight, fracPart.x);\n\n    return mix(top, bottom, fracPart.y);\n}\n\nfloat4 PixelAntialiasing(float2 uvTexspace, constant PushConstants& g_PushConstants)\n{    \n    if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0))\n        uvTexspace *= g_PushConstants.InverseDisplaySize.y * 720.0;\n    else\n        uvTexspace *= g_PushConstants.InverseDisplaySize.x * 960.0;\n\n    float2 seam = floor(uvTexspace + 0.5);\n    uvTexspace = (uvTexspace - seam) / fwidth(uvTexspace) + seam;\n    uvTexspace = clamp(uvTexspace, seam - 0.5, seam + 0.5);\n    \n    return SampleLinear(uvTexspace - 0.5, g_PushConstants);\n}\n\nfloat median(float r, float g, float b)\n{\n    return max(min(r, g), min(max(r, g), b));\n}\n\nfloat4 SampleSdfFont(float4 color, texture2d<float> texture, float2 uv, float2 screenTexSize,\n                     constant SamplerDescriptorHeap* g_SamplerDescriptorHeap,\n                     constant PushConstants& g_PushConstants)\n{\n    float4 textureColor = texture.sample(g_SamplerDescriptorHeap[0].samp, uv);\n    \n    uint width = texture.get_width();\n    uint height = texture.get_height();\n            \n    float pxRange = 8.0;\n    float2 unitRange = pxRange / float2(width, height);\n    float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0);\n            \n    float sd = median(textureColor.r, textureColor.g, textureColor.b) - 0.5;\n    float screenPxDistance = screenPxRange * (sd + g_PushConstants.Outline / (pxRange * 1.5));\n            \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TITLE_BEVEL)\n    {\n        float2 normal = normalize(float3(dfdx(sd), dfdy(sd), 0.01)).xy;\n        float3 rimColor = float3(1, 0.8, 0.29);\n        float3 shadowColor = float3(0.84, 0.57, 0);\n\n        float cosTheta = dot(normal, normalize(float2(1, 1)));\n        float3 gradient = mix(color.rgb, cosTheta >= 0.0 ? rimColor : shadowColor, abs(cosTheta));\n        color.rgb = mix(gradient, color.rgb, pow(saturate(sd + 0.77), 32.0));\n    }\n    else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_CATEGORY_BEVEL)\n    {\n        float2 normal = normalize(float3(dfdx(sd), dfdy(sd), 0.25)).xy;\n        float cosTheta = dot(normal, normalize(float2(1, 1)));\n        float gradient = 1.0 + cosTheta * 0.5;\n        color.rgb = saturate(color.rgb * gradient);\n    }\n    else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW)\n    {\n        float2 normal = normalize(float3(dfdx(sd), dfdy(sd), 0.5)).xy;\n        float cosTheta = dot(normal, normalize(float2(1, 1)));\n        float gradient = saturate(1.0 + cosTheta);\n        color.rgb = mix(color.rgb * gradient, color.rgb, pow(saturate(sd + 0.77), 32.0));\n    }\n\n    color.a *= saturate(screenPxDistance + 0.5);\n    color.a *= textureColor.a;\n    \n    return color;\n}\n\n[[fragment]]\nfloat4 shaderMain(Interpolators interpolators [[stage_in]],\n                  constant Texture2DDescriptorHeap* g_Texture2DDescriptorHeap [[buffer(0)]],\n                  constant SamplerDescriptorHeap* g_SamplerDescriptorHeap [[buffer(1)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    float4 color = interpolators.Color;\n    color *= PixelAntialiasing(interpolators.Position.xy - g_PushConstants.ProceduralOrigin, g_PushConstants);\n    \n    if (g_PushConstants.Texture2DDescriptorIndex != 0)\n    {\n        texture2d<float> texture = g_Texture2DDescriptorHeap[g_PushConstants.Texture2DDescriptorIndex & 0x7FFFFFFF].tex;\n        \n        if ((g_PushConstants.Texture2DDescriptorIndex & 0x80000000) != 0)\n        {\n            if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT)\n            {\n                float scale;\n                float invScale;\n                \n                if ((g_PushConstants.DisplaySize.x * g_PushConstants.InverseDisplaySize.y) >= (4.0 / 3.0))\n                {\n                    scale = g_PushConstants.InverseDisplaySize.y * 720.0;\n                    invScale = g_PushConstants.DisplaySize.y / 720.0;\n                }\n                else\n                {\n                    scale = g_PushConstants.InverseDisplaySize.x * 960.0;\n                    invScale = g_PushConstants.DisplaySize.x / 960.0;\n                }\n                \n                float2 lowQualityPosition = (interpolators.Position.xy - 0.5) * scale;                \n                float2 fracPart = fract(lowQualityPosition);\n                \n                float2 uvStep = fwidth(interpolators.UV) * invScale;\n                float2 lowQualityUV = interpolators.UV - fracPart * uvStep;\n                float2 screenTexSize = 1.0 / uvStep;\n                \n                float4 topLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, 0), screenTexSize, g_SamplerDescriptorHeap, g_PushConstants);\n                float4 topRight = SampleSdfFont(color, texture, lowQualityUV + float2(uvStep.x, 0), screenTexSize, g_SamplerDescriptorHeap, g_PushConstants);\n                float4 bottomLeft = SampleSdfFont(color, texture, lowQualityUV + float2(0, uvStep.y), screenTexSize, g_SamplerDescriptorHeap, g_PushConstants);\n                float4 bottomRight = SampleSdfFont(color, texture, lowQualityUV + uvStep.xy, screenTexSize, g_SamplerDescriptorHeap, g_PushConstants);\n                \n                float4 top = mix(topLeft, topRight, fracPart.x);\n                float4 bottom = mix(bottomLeft, bottomRight, fracPart.x);\n                \n                color = mix(top, bottom, fracPart.y);\n            }\n            else\n            {\n                color = SampleSdfFont(color, texture, interpolators.UV, 1.0 / fwidth(interpolators.UV), g_SamplerDescriptorHeap, g_PushConstants);\n            }\n        }\n        else\n        {\n            color *= texture.sample(g_SamplerDescriptorHeap[0].samp, interpolators.UV);\n        }\n    }\n    \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE)\n    {\n        float minAlpha = saturate((interpolators.Position.x - g_PushConstants.BoundsMin.x) / g_PushConstants.Scale.x);\n        float maxAlpha = saturate((g_PushConstants.BoundsMax.x - interpolators.Position.x) / g_PushConstants.Scale.y);\n        \n        color.a *= minAlpha;\n        color.a *= maxAlpha;\n    }\n    else if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE)\n    {\n        float minAlpha = saturate((interpolators.Position.y - g_PushConstants.BoundsMin.y) / g_PushConstants.Scale.x);\n        float maxAlpha = saturate((g_PushConstants.BoundsMax.y - interpolators.Position.y) / g_PushConstants.Scale.y);\n        \n        color.a *= minAlpha;\n        color.a *= maxAlpha;\n    }\n    else if (any(g_PushConstants.BoundsMin != g_PushConstants.BoundsMax))\n    {\n        float2 factor = saturate((interpolators.Position.xy - g_PushConstants.BoundsMin) / (g_PushConstants.BoundsMax - g_PushConstants.BoundsMin));\n        \n        if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_RECTANGLE_BEVEL)\n        {\n            float bevelSize = 0.9;\n\n            float shadow = saturate((factor.x - bevelSize) / (1.0 - bevelSize));\n            shadow = max(shadow, saturate((factor.y - bevelSize) / (1.0 - bevelSize)));\n\n            float rim = saturate((1.0 - factor.x - bevelSize) / (1.0 - bevelSize));\n            rim = max(rim, saturate((1.0 - factor.y - bevelSize) / (1.0 - bevelSize)));\n\n            float3 rimColor = float3(1, 0.8, 0.29);\n            float3 shadowColor = float3(0.84, 0.57, 0);\n\n            color.rgb = mix(color.rgb, rimColor, smoothstep(0.0, 1.0, rim));\n            color.rgb = mix(color.rgb, shadowColor, smoothstep(0.0, 1.0, shadow));\n        }\n        else\n        {\n            float4 top = mix(DecodeColor(g_PushConstants.GradientTopLeft), DecodeColor(g_PushConstants.GradientTopRight), smoothstep(0.0, 1.0, factor.x));\n            float4 bottom = mix(DecodeColor(g_PushConstants.GradientBottomLeft), DecodeColor(g_PushConstants.GradientBottomRight), smoothstep(0.0, 1.0, factor.x));\n            color *= mix(top, bottom, smoothstep(0.0, 1.0, factor.y));\n        }\n    }\n        \n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_GRAYSCALE)\n        color.rgb = dot(color.rgb, float3(0.2126, 0.7152, 0.0722));\n    \n    return color;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/imgui_vs.metal",
    "content": "#include \"imgui_common.metali\"\n\nstruct VertexStageIn\n{\n    float2 position [[attribute(0)]];\n    float2 uv [[attribute(1)]];\n    float4 color [[attribute(2)]];\n};\n\n[[vertex]]\nInterpolators shaderMain(VertexStageIn input [[stage_in]],\n                         constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    Interpolators interpolators = Interpolators{};\n\n    if (g_PushConstants.ShaderModifier == IMGUI_SHADER_MODIFIER_TEXT_SKEW)\n    {\n        if (input.position.y < g_PushConstants.Origin.y)\n            input.position.x += g_PushConstants.Scale.x;\n    }\n    else if (g_PushConstants.ShaderModifier != IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE && \n        g_PushConstants.ShaderModifier != IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE)\n    {\n        input.position.xy = g_PushConstants.Origin + (input.position.xy - g_PushConstants.Origin) * g_PushConstants.Scale;\n    }\n    \n    interpolators.Position = float4(input.position.xy * g_PushConstants.InverseDisplaySize * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0);\n    interpolators.UV = input.uv;\n    interpolators.Color = input.color;\n\n    return interpolators;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_color.metali",
    "content": "#pragma once\n\n#include \"copy_common.metali\"\n\nstruct Texture2DMSDescriptorHeap\n{\n    texture2d_ms<float> tex;\n};\n\n[[fragment]]\nfloat4 shaderMain(float4 position [[position]],\n                  constant Texture2DMSDescriptorHeap* g_Texture2DMSDescriptorHeap [[buffer(0)]],\n                  constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    float4 result = g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0);\n    \n    for (int i = 1; i < SAMPLE_COUNT; i++)\n        result += g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), i);\n\n    return result / SAMPLE_COUNT;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_color_2x.metal",
    "content": "#define SAMPLE_COUNT 2\n#include \"resolve_msaa_color.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_color_4x.metal",
    "content": "#define SAMPLE_COUNT 4\n#include \"resolve_msaa_color.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_color_8x.metal",
    "content": "#define SAMPLE_COUNT 8\n#include \"resolve_msaa_color.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_depth.metali",
    "content": "#pragma once\n\n#include \"copy_common.metali\"\n\nstruct Texture2DMSDescriptorHeap\n{\n    texture2d_ms<float> tex;\n};\n\nstruct PixelShaderOutput\n{\n    float oDepth [[depth(any)]];\n};\n\n[[fragment]]\nPixelShaderOutput shaderMain(float4 position [[position]],\n                       constant Texture2DMSDescriptorHeap* g_Texture2DMSDescriptorHeap [[buffer(0)]],\n                       constant PushConstants& g_PushConstants [[buffer(8)]])\n{\n    PixelShaderOutput output = PixelShaderOutput{};\n\n    float result = g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), 0).x;\n    \n    for (int i = 1; i < SAMPLE_COUNT; i++)\n        result = min(result, g_Texture2DMSDescriptorHeap[g_PushConstants.ResourceDescriptorIndex].tex.read(uint2(position.xy), i).x);\n\n    output.oDepth = result;\n\n    return output;\n}\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_depth_2x.metal",
    "content": "#define SAMPLE_COUNT 2\n#include \"resolve_msaa_depth.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_depth_4x.metal",
    "content": "#define SAMPLE_COUNT 4\n#include \"resolve_msaa_depth.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/shader/msl/resolve_msaa_depth_8x.metal",
    "content": "#define SAMPLE_COUNT 8\n#include \"resolve_msaa_depth.metali\"\n"
  },
  {
    "path": "MarathonRecomp/gpu/video.cpp",
    "content": "#include \"video.h\"\n\n#include \"imgui/imgui_common.h\"\n#include \"imgui/imgui_snapshot.h\"\n#include \"imgui/imgui_font_builder.h\"\n\n#include <app.h>\n#include <bc_diff.h>\n#include <cpu/guest_thread.h>\n#include <cstdint>\n#include <cstdio>\n#include <decompressor.h>\n#include <kernel/function.h>\n#include <kernel/heap.h>\n#include <hid/hid.h>\n#include <kernel/memory.h>\n#include <kernel/xdbf.h>\n#include <plume_render_interface.h>\n#include <res/bc_diff/button_bc_diff.bin.h>\n#include <res/font/im_font_atlas.dds.h>\n#include <shader/shader_cache.h>\n#include <Marathon.h>\n#include <ui/achievement_menu.h>\n#include <ui/achievement_overlay.h>\n#include <ui/button_window.h>\n#include <ui/fader.h>\n#include <ui/imgui_utils.h>\n#include <ui/installer_wizard.h>\n#include <ui/message_window.h>\n#include <ui/options_menu.h>\n#include <ui/game_window.h>\n#include <ui/black_bar.h>\n#include <patches/aspect_ratio_patches.h>\n#include <user/config.h>\n#include <sdl_listener.h>\n#include <xxHashMap.h>\n#include <os/process.h>\n\n#if defined(ASYNC_PSO_DEBUG) || defined(PSO_CACHING)\n#include <magic_enum/magic_enum.hpp>\n#endif\n\n#define MARATHON_RECOMP\n#include \"../../tools/XenosRecomp/XenosRecomp/shader_common.h\"\n\n#ifdef MARATHON_RECOMP_D3D12\n#include \"shader/hlsl/blend_color_alpha_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/conditional_survey_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/copy_vs.hlsl.dxil.h\"\n#include \"shader/hlsl/copy_color_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/copy_depth_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/csd_filter_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/csd_no_tex_vs.hlsl.dxil.h\"\n#include \"shader/hlsl/csd_vs.hlsl.dxil.h\"\n#include \"shader/hlsl/enhanced_burnout_blur_vs.hlsl.dxil.h\"\n#include \"shader/hlsl/enhanced_burnout_blur_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/gamma_correction_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/gaussian_blur_3x3.hlsl.dxil.h\"\n#include \"shader/hlsl/gaussian_blur_5x5.hlsl.dxil.h\"\n#include \"shader/hlsl/gaussian_blur_7x7.hlsl.dxil.h\"\n#include \"shader/hlsl/gaussian_blur_9x9.hlsl.dxil.h\"\n#include \"shader/hlsl/imgui_ps.hlsl.dxil.h\"\n#include \"shader/hlsl/imgui_vs.hlsl.dxil.h\"\n#include \"shader/hlsl/resolve_msaa_color_2x.hlsl.dxil.h\"\n#include \"shader/hlsl/resolve_msaa_color_4x.hlsl.dxil.h\"\n#include \"shader/hlsl/resolve_msaa_color_8x.hlsl.dxil.h\"\n#include \"shader/hlsl/resolve_msaa_depth_2x.hlsl.dxil.h\"\n#include \"shader/hlsl/resolve_msaa_depth_4x.hlsl.dxil.h\"\n#include \"shader/hlsl/resolve_msaa_depth_8x.hlsl.dxil.h\"\n#endif\n\n#ifdef MARATHON_RECOMP_METAL\n#include \"shader/msl/blend_color_alpha_ps.metal.metallib.h\"\n#include \"shader/msl/conditional_survey_ps.metal.metallib.h\"\n#include \"shader/msl/copy_vs.metal.metallib.h\"\n#include \"shader/msl/copy_color_ps.metal.metallib.h\"\n#include \"shader/msl/copy_depth_ps.metal.metallib.h\"\n#include \"shader/msl/csd_filter_ps.metal.metallib.h\"\n#include \"shader/msl/csd_no_tex_vs.metal.metallib.h\"\n#include \"shader/msl/csd_vs.metal.metallib.h\"\n#include \"shader/msl/enhanced_burnout_blur_vs.metal.metallib.h\"\n#include \"shader/msl/enhanced_burnout_blur_ps.metal.metallib.h\"\n#include \"shader/msl/gamma_correction_ps.metal.metallib.h\"\n#include \"shader/msl/gaussian_blur_3x3.metal.metallib.h\"\n#include \"shader/msl/gaussian_blur_5x5.metal.metallib.h\"\n#include \"shader/msl/gaussian_blur_7x7.metal.metallib.h\"\n#include \"shader/msl/gaussian_blur_9x9.metal.metallib.h\"\n#include \"shader/msl/imgui_ps.metal.metallib.h\"\n#include \"shader/msl/imgui_vs.metal.metallib.h\"\n#include \"shader/msl/resolve_msaa_color_2x.metal.metallib.h\"\n#include \"shader/msl/resolve_msaa_color_4x.metal.metallib.h\"\n#include \"shader/msl/resolve_msaa_color_8x.metal.metallib.h\"\n#include \"shader/msl/resolve_msaa_depth_2x.metal.metallib.h\"\n#include \"shader/msl/resolve_msaa_depth_4x.metal.metallib.h\"\n#include \"shader/msl/resolve_msaa_depth_8x.metal.metallib.h\"\n#endif\n\n#include \"shader/hlsl/blend_color_alpha_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/conditional_survey_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/copy_vs.hlsl.spirv.h\"\n#include \"shader/hlsl/copy_color_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/copy_depth_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/csd_filter_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/csd_no_tex_vs.hlsl.spirv.h\"\n#include \"shader/hlsl/csd_vs.hlsl.spirv.h\"\n#include \"shader/hlsl/enhanced_burnout_blur_vs.hlsl.spirv.h\"\n#include \"shader/hlsl/enhanced_burnout_blur_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/gamma_correction_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/gaussian_blur_3x3.hlsl.spirv.h\"\n#include \"shader/hlsl/gaussian_blur_5x5.hlsl.spirv.h\"\n#include \"shader/hlsl/gaussian_blur_7x7.hlsl.spirv.h\"\n#include \"shader/hlsl/gaussian_blur_9x9.hlsl.spirv.h\"\n#include \"shader/hlsl/imgui_ps.hlsl.spirv.h\"\n#include \"shader/hlsl/imgui_vs.hlsl.spirv.h\"\n#include \"shader/hlsl/resolve_msaa_color_2x.hlsl.spirv.h\"\n#include \"shader/hlsl/resolve_msaa_color_4x.hlsl.spirv.h\"\n#include \"shader/hlsl/resolve_msaa_color_8x.hlsl.spirv.h\"\n#include \"shader/hlsl/resolve_msaa_depth_2x.hlsl.spirv.h\"\n#include \"shader/hlsl/resolve_msaa_depth_4x.hlsl.spirv.h\"\n#include \"shader/hlsl/resolve_msaa_depth_8x.hlsl.spirv.h\"\n\n#ifdef _WIN32\nextern \"C\"\n{\n    __declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;\n    __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;\n}\n#endif\n\nnamespace plume\n{\n#ifdef MARATHON_RECOMP_D3D12\n    extern std::unique_ptr<RenderInterface> CreateD3D12Interface();\n#endif\n#ifdef MARATHON_RECOMP_METAL\nextern std::unique_ptr<RenderInterface> CreateMetalInterface();\n#endif\n#ifdef PLUME_SDL_VULKAN_ENABLED\n    extern std::unique_ptr<RenderInterface> CreateVulkanInterface(RenderWindow sdlWindow);\n#else\n    extern std::unique_ptr<RenderInterface> CreateVulkanInterface();\n#endif\n\n    static std::unique_ptr<RenderInterface> CreateVulkanInterfaceWrapper() {\n#ifdef PLUME_SDL_VULKAN_ENABLED\n        return CreateVulkanInterface(GameWindow::s_renderWindow);\n#else\n        return CreateVulkanInterface();\n#endif\n    }\n}\n\nusing namespace plume;\n\n#pragma pack(push, 1)\nstruct PipelineState\n{\n    GuestShader* vertexShader = nullptr;\n    GuestShader* pixelShader = nullptr;\n    GuestVertexDeclaration* vertexDeclaration = nullptr;\n    bool zEnable = true;\n    bool zWriteEnable = true;\n    bool stencilEnable = false;\n    bool stencilTwoSided = false;\n    RenderBlend srcBlend = RenderBlend::ONE;\n    RenderBlend destBlend = RenderBlend::ZERO;\n    RenderCullMode cullMode = RenderCullMode::NONE;\n    RenderFrontFace frontFace = RenderFrontFace::CLOCKWISE;\n    RenderComparisonFunction zFunc = RenderComparisonFunction::LESS;\n    RenderComparisonFunction stencilFunc = RenderComparisonFunction::ALWAYS;\n    RenderStencilOp stencilFail = RenderStencilOp::KEEP;\n    RenderStencilOp stencilZFail = RenderStencilOp::KEEP;\n    RenderStencilOp stencilPass = RenderStencilOp::KEEP;\n    RenderComparisonFunction stencilFuncCCW = RenderComparisonFunction::ALWAYS;\n    RenderStencilOp stencilFailCCW = RenderStencilOp::KEEP;\n    RenderStencilOp stencilZFailCCW = RenderStencilOp::KEEP;\n    RenderStencilOp stencilPassCCW = RenderStencilOp::KEEP;\n    uint32_t stencilMask = 0xFFFFFFFF;\n    uint32_t stencilWriteMask = 0xFFFFFFFF;\n    uint32_t stencilRef = 0;\n    bool alphaBlendEnable = false;\n    RenderBlendOperation blendOp = RenderBlendOperation::ADD;\n    float slopeScaledDepthBias = 0.0f;\n    int32_t depthBias = 0;\n    RenderBlend srcBlendAlpha = RenderBlend::ONE;\n    RenderBlend destBlendAlpha = RenderBlend::ZERO;\n    RenderBlendOperation blendOpAlpha = RenderBlendOperation::ADD;\n    uint32_t colorWriteEnable = uint32_t(RenderColorWriteEnable::ALL);\n    RenderPrimitiveTopology primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST;\n    uint8_t vertexStrides[16]{};\n    RenderFormat renderTargetFormat{};\n    RenderFormat depthStencilFormat{};\n    RenderSampleCounts sampleCount = RenderSampleCount::COUNT_1;\n    bool enableAlphaToCoverage = false;\n    bool enableConditionalSurvey = false;\n    uint32_t specConstants = 0;\n};\n#pragma pack(pop)\n\nstruct UploadAllocation\n{\n    const RenderBuffer* buffer;\n    uint64_t offset;\n    uint8_t* memory;\n    uint64_t deviceAddress;\n};\n\nstruct SharedConstants\n{\n    uint32_t texture2DIndices[16]{};\n    uint32_t texture2DArrayIndices[16]{};\n    uint32_t textureCubeIndices[16]{};\n    uint32_t samplerIndices[16]{};\n    uint32_t booleans{};\n    uint32_t swappedTexcoords{};\n    uint32_t swappedNormals{};\n    uint32_t swappedBinormals{};\n    uint32_t swappedTangents{};\n    uint32_t swappedBlendWeights{};\n    float halfPixelOffsetX{};\n    float halfPixelOffsetY{};\n    float clipPlane[4]{};\n    bool clipPlaneEnabled{};\n    float alphaThreshold{};\n    uint32_t conditionalSurveyIndex{};\n    uint32_t conditionalRenderingIndex{};\n};\n\n// Depth bias values here are only used when the render device has \n// dynamic depth bias capability enabled. Otherwise, they get unused\n// and the values get assigned in the pipeline state instead.\n\nstatic GuestSurface* g_renderTarget;\nstatic GuestSurface* g_depthStencil;\nstatic RenderFramebuffer* g_framebuffer;\nstatic RenderViewport g_viewport(0.0f, 0.0f, 1280.0f, 720.0f);\nstatic PipelineState g_pipelineState;\nstatic int32_t g_depthBias;\nstatic float g_slopeScaledDepthBias;\nstatic uint32_t g_vertexShaderConstants[0x400];\nstatic uint32_t g_pixelShaderConstants[0x380];\nstatic SharedConstants g_sharedConstants;\nstatic GuestTexture* g_textures[16];\nstatic RenderSamplerDesc g_samplerDescs[16];\nstatic bool g_scissorTestEnable = false;\nstatic RenderRect g_scissorRect;\nstatic RenderVertexBufferView g_vertexBufferViews[16];\nstatic RenderInputSlot g_inputSlots[16];\nstatic RenderIndexBufferView g_indexBufferView({}, 0, RenderFormat::R16_UINT);\n\nstruct DirtyStates\n{\n    bool renderTargetAndDepthStencil;\n    bool viewport;\n    bool pipelineState;\n    bool depthBias;\n    bool sharedConstants;\n    bool scissorRect;\n    bool vertexShaderConstants;\n    uint8_t vertexStreamFirst;\n    uint8_t vertexStreamLast;\n    bool indices;\n    bool pixelShaderConstants;\n\n    DirtyStates(bool value)\n        : renderTargetAndDepthStencil(value)\n        , viewport(value)\n        , pipelineState(value)\n        , depthBias(value)\n        , sharedConstants(value)\n        , scissorRect(value)\n        , vertexShaderConstants(value)\n        , vertexStreamFirst(value ? 0 : 255)\n        , vertexStreamLast(value ? 15 : 0)\n        , indices(value)\n        , pixelShaderConstants(value)\n    {\n    }\n};\n\nstatic DirtyStates g_dirtyStates(true);\n\ntemplate<typename T>\nstatic void SetDirtyValue(bool& dirtyState, T& dest, const T& src)\n{\n    if (dest != src)\n    {\n        dest = src;\n        dirtyState = true;\n    }\n}\n\nstatic constexpr size_t PROFILER_VALUE_COUNT = 256;\nstatic size_t g_profilerValueIndex;\n\nstruct Profiler\n{\n    std::atomic<double> value;\n    double values[PROFILER_VALUE_COUNT];\n    std::chrono::steady_clock::time_point start;\n\n    void Begin()\n    {\n        start = std::chrono::steady_clock::now();\n    }\n\n    void End()\n    {\n        value = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();\n    }\n\n    void Set(double v)\n    {\n        value = v;\n    }\n\n    void Reset()\n    {\n        End();\n        Begin();\n    }\n\n    double UpdateAndReturnAverage()\n    {\n        values[g_profilerValueIndex] = value;\n        return std::accumulate(values, values + PROFILER_VALUE_COUNT, 0.0) / PROFILER_VALUE_COUNT;\n    }\n};\n\nstatic double g_applicationValues[PROFILER_VALUE_COUNT];\nstatic Profiler g_gpuFrameProfiler;\nstatic Profiler g_presentProfiler;\nstatic Profiler g_frameFenceProfiler;\nstatic Profiler g_presentWaitProfiler;\nstatic Profiler g_swapChainAcquireProfiler;\n\nstatic bool g_profilerVisible;\nstatic bool g_profilerWasToggled;\n\n#if !defined(MARATHON_RECOMP_D3D12) && !defined(MARATHON_RECOMP_METAL)\nstatic constexpr Backend g_backend = Backend::VULKAN;\n#else\nstatic Backend g_backend;\n#endif\n\nstatic bool g_triangleStripWorkaround = false;\n\nstatic std::unique_ptr<RenderInterface> g_interface;\nstatic std::unique_ptr<RenderDevice> g_device;\n\nstatic RenderDeviceCapabilities g_capabilities;\n\nstatic constexpr size_t NUM_FRAMES = 2;\nstatic constexpr size_t NUM_QUERIES = 2;\n\nstatic uint32_t g_frame = 0;\nstatic uint32_t g_nextFrame = 1;\n\nstatic std::unique_ptr<RenderCommandQueue> g_queue;\nstatic std::unique_ptr<RenderCommandList> g_commandLists[NUM_FRAMES];\nstatic std::unique_ptr<RenderCommandFence> g_commandFences[NUM_FRAMES];\nstatic std::unique_ptr<RenderQueryPool> g_queryPools[NUM_FRAMES];\nstatic bool g_commandListStates[NUM_FRAMES];\n\nstatic Mutex g_copyMutex;\nstatic std::unique_ptr<RenderCommandQueue> g_copyQueue;\nstatic std::unique_ptr<RenderCommandList> g_copyCommandList;\nstatic std::unique_ptr<RenderCommandFence> g_copyCommandFence;\n\nstatic Mutex g_discardMutex;\nstatic std::unique_ptr<RenderCommandList> g_discardCommandList;\nstatic std::unique_ptr<RenderCommandFence> g_discardCommandFence;\n\nstatic std::unique_ptr<RenderSwapChain> g_swapChain;\nstatic bool g_swapChainValid;\n\nstatic constexpr RenderFormat BACKBUFFER_FORMAT = RenderFormat::B8G8R8A8_UNORM;\n\nstatic std::unique_ptr<RenderCommandSemaphore> g_acquireSemaphores[NUM_FRAMES];\nstatic std::unique_ptr<RenderCommandSemaphore> g_renderSemaphores[NUM_FRAMES];\nstatic uint32_t g_backBufferIndex;\nstatic std::unique_ptr<GuestSurface> g_backBufferHolder;\nstatic GuestSurface* g_backBuffer;\n\nstatic std::unique_ptr<RenderTexture> g_intermediaryBackBufferTexture;\nstatic uint32_t g_intermediaryBackBufferTextureWidth;\nstatic uint32_t g_intermediaryBackBufferTextureHeight;\nstatic uint32_t g_intermediaryBackBufferTextureDescriptorIndex;\n\nstatic std::unique_ptr<RenderPipeline> g_gammaCorrectionPipeline;\n\nstatic std::unique_ptr<RenderDescriptorSet> g_textureDescriptorSet;\nstatic std::unique_ptr<RenderDescriptorSet> g_samplerDescriptorSet;\n\nstatic constexpr uint32_t CONDITIONAL_SURVEY_MAX = 64;\nstatic std::unique_ptr<RenderBuffer> g_conditionalSurveyBuffer;\nstatic std::unique_ptr<RenderDescriptorSet> g_conditionalSurveyDescriptorSet;\n\nenum\n{\n    TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D,\n    TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY,\n    TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE,\n    TEXTURE_DESCRIPTOR_NULL_COUNT\n};\n\nstruct TextureDescriptorAllocator\n{\n    Mutex mutex;\n    uint32_t capacity = TEXTURE_DESCRIPTOR_NULL_COUNT;\n    std::vector<uint32_t> freed;\n\n    uint32_t allocate()\n    {\n        std::lock_guard lock(mutex);\n\n        uint32_t value;\n        if (!freed.empty())\n        {\n            value = freed.back();\n            freed.pop_back();\n        }\n        else\n        {\n            value = capacity;\n            ++capacity;\n        }\n\n        return value;\n    }\n\n    void free(uint32_t value)\n    {\n        assert(value != NULL);\n        std::lock_guard lock(mutex);\n        freed.push_back(value);\n    }\n};\n\nstatic std::unique_ptr<RenderTexture> g_blankTextures[TEXTURE_DESCRIPTOR_NULL_COUNT];\nstatic std::unique_ptr<RenderTextureView> g_blankTextureViews[TEXTURE_DESCRIPTOR_NULL_COUNT];\n\nstatic TextureDescriptorAllocator g_textureDescriptorAllocator;\n\nstatic std::unique_ptr<RenderPipelineLayout> g_pipelineLayout;\nstatic xxHashMap<std::unique_ptr<RenderPipeline>> g_pipelines;\n\n#ifdef ASYNC_PSO_DEBUG\nstatic std::atomic<uint32_t> g_pipelinesCreatedInRenderThread;\nstatic std::atomic<uint32_t> g_pipelinesCreatedAsynchronously;\nstatic std::atomic<uint32_t> g_pipelinesDropped;\nstatic std::atomic<uint32_t> g_pipelinesCurrentlyCompiling;\nstatic std::string g_pipelineDebugText;\nstatic Mutex g_debugMutex;\n#endif\n\n#ifdef PSO_CACHING\nstatic xxHashMap<PipelineState> g_pipelineStatesToCache;\nstatic Mutex g_pipelineCacheMutex;\n#endif\n\nstatic std::atomic<uint32_t> g_compilingPipelineTaskCount;\nstatic std::atomic<uint32_t> g_pendingPipelineTaskCount;\n\nenum class PipelineTaskType\n{\n    Null,\n    DatabaseData,\n    PrecompilePipelines,\n    RecompilePipelines\n};\n\nstruct PipelineTask\n{\n    PipelineTaskType type{};\n//    boost::shared_ptr<Hedgehog::Database::CDatabaseData> databaseData;\n};\n\nstatic Mutex g_pipelineTaskMutex;\nstatic std::vector<PipelineTask> g_pipelineTaskQueue;\n\n//static void EnqueuePipelineTask(PipelineTaskType type, const boost::shared_ptr<Hedgehog::Database::CDatabaseData>& databaseData)\n//{\n//    // Precompiled pipelines deliberately do not increment\n//    // this counter to overlap the compilation with intro logos.\n//    if (type != PipelineTaskType::PrecompilePipelines)\n//        ++g_compilingPipelineTaskCount;\n//\n//    {\n//        std::lock_guard lock(g_pipelineTaskMutex);\n//        g_pipelineTaskQueue.emplace_back(type, databaseData);\n//    }\n//\n//    if ((++g_pendingPipelineTaskCount) == 1)\n//        g_pendingPipelineTaskCount.notify_one();\n//}\n\nstatic const PipelineState g_pipelineStateCache[] =\n{\n#include \"cache/pipeline_state_cache.h\"\n};\n\n#include \"cache/vertex_element_cache.h\"\n\nstatic uint8_t* const g_vertexDeclarationCache[] =\n{\n#include \"cache/vertex_declaration_cache.h\"\n};\n\nstatic xxHashMap<std::pair<uint32_t, std::unique_ptr<RenderSampler>>> g_samplerStates;\n\nstatic Mutex g_vertexDeclarationMutex;\nstatic xxHashMap<GuestVertexDeclaration*> g_vertexDeclarations;\n\nstruct UploadBuffer\n{\n    static constexpr size_t SIZE = 16 * 1024 * 1024;\n\n    std::unique_ptr<RenderBuffer> buffer;\n    uint8_t* memory = nullptr;\n    uint64_t deviceAddress = 0;\n};\n\nstruct UploadAllocator\n{\n    std::vector<UploadBuffer> buffers;\n    uint32_t index = 0;\n    uint32_t offset = 0;\n\n    UploadAllocation allocate(uint32_t size, uint32_t alignment)\n    {\n        assert(size <= UploadBuffer::SIZE);\n\n        offset = (offset + alignment - 1) & ~(alignment - 1);\n\n        if (offset + size > UploadBuffer::SIZE)\n        {\n            ++index;\n            offset = 0;\n        }\n\n        if (buffers.size() <= index)\n            buffers.resize(index + 1);\n\n        auto& buffer = buffers[index];\n        if (buffer.buffer == nullptr)\n        {\n            buffer.buffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(UploadBuffer::SIZE, RenderBufferFlag::CONSTANT | RenderBufferFlag::VERTEX | RenderBufferFlag::INDEX | RenderBufferFlag::DEVICE_ADDRESSABLE));\n            buffer.memory = reinterpret_cast<uint8_t*>(buffer.buffer->map());\n            buffer.deviceAddress = buffer.buffer->getDeviceAddress();\n        }\n        \n        auto ref = buffer.buffer->at(offset);\n        offset += size;\n\n        return { ref.ref, ref.offset, buffer.memory + ref.offset, buffer.deviceAddress + ref.offset };\n    }\n\n    template<bool TByteSwap, typename T>\n    UploadAllocation allocate(const T* memory, uint32_t size, uint32_t alignment)\n    {\n        auto result = allocate(size, alignment);\n\n        if constexpr (TByteSwap)\n        {\n            auto destination = reinterpret_cast<T*>(result.memory);\n\n            for (size_t i = 0; i < size; i += sizeof(T))\n            {\n                *destination = ByteSwap(*memory);\n                ++destination;\n                ++memory;\n            }\n        }\n        else\n        {\n            memcpy(result.memory, memory, size);\n        }\n\n        return result;\n    }\n\n    void reset()\n    {\n        index = 0;\n        offset = 0;\n    }\n};\n\nstatic UploadAllocator g_uploadAllocators[NUM_FRAMES];\n\nstruct IntermediaryUploadAllocator\n{\n    static constexpr size_t SIZE = 16 * 1024 * 1024;\n\n    std::vector<std::unique_ptr<uint8_t[]>> buffers;\n    uint32_t index = 0;\n    uint32_t offset = 0;\n\n    uint8_t* allocate(uint32_t size)\n    {\n        assert(size <= SIZE);\n\n        if (offset + size > SIZE)\n        {\n            ++index;\n            offset = 0;\n        }\n\n        if (buffers.size() <= index)\n            buffers.resize(index + 1);\n\n        auto& buffer = buffers[index];\n        if (buffer == nullptr)\n            buffer = std::make_unique_for_overwrite<uint8_t[]>(SIZE);\n\n        auto result = buffer.get() + offset;\n        offset += ((size + 0xF) & ~0xF);\n\n        return result;\n    }\n\n    uint8_t* allocate(const void* memory, uint32_t size)\n    {\n        auto result = allocate(size);\n        memcpy(result, memory, size);\n        return result;\n    }\n\n    void reset()\n    {\n        index = 0;\n        offset = 0;\n    }\n};\n\nstatic IntermediaryUploadAllocator g_intermediaryUploadAllocator;\n\nstatic std::vector<GuestResource*> g_tempResources[NUM_FRAMES];\nstatic std::vector<std::unique_ptr<RenderBuffer>> g_tempBuffers[NUM_FRAMES];\n\ntemplate<GuestPrimitiveType PrimitiveType>\nstruct PrimitiveIndexData\n{\n    std::vector<uint16_t> indexData;\n    RenderBufferReference indexBuffer;\n    uint32_t currentIndexCount = 0;\n\n    uint32_t prepare(uint32_t guestPrimCount)\n    {\n        uint32_t primCount;\n        uint32_t indexCountPerPrimitive;\n\n        switch (PrimitiveType)\n        {\n        case D3DPT_TRIANGLEFAN:\n            primCount = guestPrimCount - 2;\n            indexCountPerPrimitive = 3; \n            break;\n        case D3DPT_QUADLIST:\n            primCount = guestPrimCount / 4;\n            indexCountPerPrimitive = 6;\n            break;\n        default:\n            assert(false && \"Unknown primitive type.\");\n            break;\n        }\n\n        uint32_t indexCount = primCount * indexCountPerPrimitive;\n\n        if (indexData.size() < indexCount)\n        {\n            const size_t oldPrimCount = indexData.size() / indexCountPerPrimitive;\n            indexData.resize(indexCount);\n\n            for (size_t i = oldPrimCount; i < primCount; i++)\n            {\n                switch (PrimitiveType)\n                {\n                case D3DPT_TRIANGLEFAN:\n                {\n                    indexData[i * 3 + 0] = 0;\n                    indexData[i * 3 + 1] = static_cast<uint16_t>(i + 1);\n                    indexData[i * 3 + 2] = static_cast<uint16_t>(i + 2);\n                    break;\n                }\n                case D3DPT_QUADLIST:\n                {\n                    indexData[i * 6 + 0] = static_cast<uint16_t>(i * 4 + 0);\n                    indexData[i * 6 + 1] = static_cast<uint16_t>(i * 4 + 1);\n                    indexData[i * 6 + 2] = static_cast<uint16_t>(i * 4 + 2);\n\n                    indexData[i * 6 + 3] = static_cast<uint16_t>(i * 4 + 0);\n                    indexData[i * 6 + 4] = static_cast<uint16_t>(i * 4 + 2);\n                    indexData[i * 6 + 5] = static_cast<uint16_t>(i * 4 + 3);\n                    break;\n                }\n                default:\n                    assert(false && \"Unknown primitive type.\");\n                    break;\n                }\n            }\n        }\n\n        if (indexBuffer == NULL || currentIndexCount < indexCount)\n        {\n            auto allocation = g_uploadAllocators[g_frame].allocate<false>(indexData.data(), indexCount * 2, 2);\n            indexBuffer = allocation.buffer->at(allocation.offset);\n            currentIndexCount = indexCount;\n        }\n\n        SetDirtyValue(g_dirtyStates.indices, g_indexBufferView.buffer, indexBuffer);\n        SetDirtyValue(g_dirtyStates.indices, g_indexBufferView.size, indexCount * 2);\n        SetDirtyValue(g_dirtyStates.indices, g_indexBufferView.format, RenderFormat::R16_UINT);\n\n        return indexCount;\n    }\n\n    void reset()\n    {\n        indexBuffer = {};\n        currentIndexCount = 0;\n    }\n};\n\nstatic PrimitiveIndexData<D3DPT_TRIANGLEFAN> g_triangleFanIndexData;\nstatic PrimitiveIndexData<D3DPT_QUADLIST> g_quadIndexData;\n\nstatic void DestructTempResources()\n{\n    for (auto resource : g_tempResources[g_frame])\n    {\n        switch (resource->type)\n        {\n        case ResourceType::Texture:\n        case ResourceType::VolumeTexture:\n        case ResourceType::ArrayTexture:\n        {\n            const auto texture = reinterpret_cast<GuestTexture*>(resource);\n\n            if (texture->mappedMemory != nullptr) {\n                g_userHeap.Free(texture->mappedMemory);\n            }\n\n            g_textureDescriptorSet->setTexture(texture->descriptorIndex, nullptr, {});\n            g_textureDescriptorAllocator.free(texture->descriptorIndex);\n\n            if (texture->patchedTexture != nullptr)\n            {\n                g_textureDescriptorSet->setTexture(texture->patchedTexture->descriptorIndex, nullptr, {});\n                g_textureDescriptorAllocator.free(texture->patchedTexture->descriptorIndex);\n            }\n\n            texture->~GuestTexture();\n            break;\n        }\n\n        case ResourceType::VertexBuffer:\n        case ResourceType::IndexBuffer:\n        {\n            const auto buffer = reinterpret_cast<GuestBuffer*>(resource);\n\n\n            if (buffer->mappedMemory != nullptr)\n                g_userHeap.Free(buffer->mappedMemory);\n\n            buffer->~GuestBuffer();\n            break;\n        }\n\n        case ResourceType::RenderTarget:\n        case ResourceType::DepthStencil:\n        {\n            const auto surface = reinterpret_cast<GuestSurface*>(resource);\n\n            if (surface->descriptorIndex != NULL)\n            {\n                g_textureDescriptorSet->setTexture(surface->descriptorIndex, nullptr, {});\n                g_textureDescriptorAllocator.free(surface->descriptorIndex);\n            }\n\n            surface->~GuestSurface();\n            break;\n        }\n\n        case ResourceType::VertexDeclaration:\n            reinterpret_cast<GuestVertexDeclaration*>(resource)->~GuestVertexDeclaration();\n            break;\n\n        case ResourceType::VertexShader:\n        case ResourceType::PixelShader:\n        {\n            reinterpret_cast<GuestShader*>(resource)->~GuestShader();\n            break;\n        }\n        }\n\n        g_userHeap.Free(resource);\n    }\n\n    g_tempResources[g_frame].clear();\n    g_tempBuffers[g_frame].clear();\n}\n\nstatic std::thread::id g_presentThreadId = std::this_thread::get_id();\nstatic std::atomic<bool> g_readyForCommands;\n\n// PPC_FUNC_IMPL(__imp__sub_824ECA00);\n// PPC_FUNC(sub_824ECA00)\n// {\n//     g_readyForCommands.wait(false);\n//     g_presentThreadId = std::this_thread::get_id();\n//     __imp__sub_824ECA00(ctx, base);\n// }\n\nstatic ankerl::unordered_dense::map<RenderTexture*, RenderTextureLayout> g_barrierMap;\n\nstatic void AddBarrier(GuestBaseTexture* texture, RenderTextureLayout layout)\n{\n    if (texture != nullptr && texture->layout != layout)\n    {\n        g_barrierMap[texture->texture] = layout;\n        texture->layout = layout;\n    }\n}\n\nstatic std::vector<RenderTextureBarrier> g_barriers;\n\nstatic void FlushBarriers()\n{\n    if (!g_barrierMap.empty())\n    {\n        for (auto& [texture, layout] : g_barrierMap)\n            g_barriers.emplace_back(texture, layout);\n\n        g_commandLists[g_frame]->barriers(RenderBarrierStage::GRAPHICS | RenderBarrierStage::COPY, g_barriers);\n\n        g_barrierMap.clear();\n        g_barriers.clear();\n    }\n}\n\nstatic std::unique_ptr<uint8_t[]> g_shaderCache;\nstatic std::unique_ptr<uint8_t[]> g_buttonBcDiff;\n\nstatic void LoadEmbeddedResources()\n{\n    switch (g_backend)\n    {\n    case Backend::VULKAN:\n        g_shaderCache = std::make_unique<uint8_t[]>(g_spirvCacheDecompressedSize);\n        ZSTD_decompress(g_shaderCache.get(), g_spirvCacheDecompressedSize, g_compressedSpirvCache, g_spirvCacheCompressedSize);\n        break;\n#if defined(MARATHON_RECOMP_D3D12)\n    case Backend::D3D12:\n        g_shaderCache = std::make_unique<uint8_t[]>(g_dxilCacheDecompressedSize);\n        ZSTD_decompress(g_shaderCache.get(), g_dxilCacheDecompressedSize, g_compressedDxilCache, g_dxilCacheCompressedSize);\n        break;\n#elif defined(MARATHON_RECOMP_METAL)\n    case Backend::METAL:\n        g_shaderCache = std::make_unique<uint8_t[]>(g_airCacheDecompressedSize);\n        ZSTD_decompress(g_shaderCache.get(), g_airCacheDecompressedSize, g_compressedAirCache, g_airCacheCompressedSize);\n        break;\n#endif\n    default:\n        assert(false);\n    }\n\n    g_buttonBcDiff = decompressZstd(g_button_bc_diff, g_button_bc_diff_uncompressed_size);\n}\n\nenum class CsdFilterState\n{\n    Unknown,\n    On,\n    Off\n};\n\nstatic CsdFilterState g_csdFilterState;\n\nstatic ankerl::unordered_dense::set<GuestSurface*> g_pendingSurfaceCopies;\nstatic ankerl::unordered_dense::set<GuestSurface*> g_pendingResolves;\n\nenum class RenderCommandType\n{\n    SetRenderState,\n    DestructResource,\n    UnlockTextureRect,\n    UnlockBuffer16,\n    UnlockBuffer32,\n    DrawImGui,\n    ExecuteCommandList,\n    BeginCommandList,\n    StretchRect,\n    SetRenderTarget,\n    SetDepthStencilSurface,\n    ExecutePendingStretchRectCommands,\n    Clear,\n    SetViewport,\n    SetTexture,\n    SetScissorRect,\n    SetSamplerState,\n    SetBooleans,\n    SetVertexShaderConstants,\n    SetPixelShaderConstants,\n    AddPipeline,\n    DrawPrimitive,\n    DrawIndexedPrimitive,\n    DrawPrimitiveUP,\n    SetVertexDeclaration,\n    SetVertexShader,\n    SetStreamSource,\n    SetIndices,\n    SetPixelShader,\n    SetConditionalSurvey,\n    SetConditionalRendering,\n};\n\nstruct RenderCommand\n{\n    RenderCommandType type;\n    union\n    {\n        struct\n        {\n            GuestRenderState type;\n            uint32_t value;\n        } setRenderState;\n\n        struct \n        {\n            GuestResource* resource;\n        } destructResource;\n\n        struct\n        {\n            GuestTexture* texture;\n        } unlockTextureRect;\n\n        struct\n        {\n            GuestBuffer* buffer;\n        } unlockBuffer;\n\n        struct \n        {\n            GuestDevice* device;\n            uint32_t flags;\n            GuestTexture* texture;\n            uint32_t destSliceOrFace;\n        } stretchRect;\n\n        struct \n        {\n            GuestSurface* renderTarget;\n        } setRenderTarget;\n\n        struct \n        {\n            GuestSurface* depthStencil;\n        } setDepthStencilSurface;\n\n        struct \n        {\n            uint32_t flags;\n            float color[4];\n            float z;\n            uint32_t stencil;\n        } clear;\n\n        struct \n        {\n            float x;\n            float y;\n            float width;\n            float height;\n            float minDepth;\n            float maxDepth;\n        } setViewport;\n\n        struct \n        {\n            uint32_t index;\n            GuestTexture* texture;\n        } setTexture;\n\n        struct \n        {\n            int32_t left;\n            int32_t top;\n            int32_t right;\n            int32_t bottom;\n        } setScissorRect;\n\n        struct\n        {\n            uint32_t index;\n            uint32_t data0;\n            uint32_t data3;\n            uint32_t data5;\n        } setSamplerState;\n\n        struct\n        {\n            uint32_t booleans;\n        } setBooleans;\n\n        struct\n        {\n            uint8_t* memory;\n            uint32_t index;\n            uint32_t size;\n        } setVertexShaderConstants;  \n        \n        struct\n        {\n            uint8_t* memory;\n            uint32_t index;\n            uint32_t size;\n        } setPixelShaderConstants;\n\n        struct\n        {\n            XXH64_hash_t hash;\n            RenderPipeline* pipeline;\n        } addPipeline;\n\n        struct \n        {\n            uint32_t primitiveType; \n            uint32_t startVertex; \n            uint32_t primitiveCount;\n        } drawPrimitive;\n\n        struct \n        {\n            uint32_t primitiveType;\n            int32_t baseVertexIndex; \n            uint32_t startIndex;\n            uint32_t primCount;\n        } drawIndexedPrimitive;\n\n        struct \n        {\n            uint32_t primitiveType;\n            uint32_t primitiveCount; \n            uint8_t* vertexStreamZeroData;\n            uint32_t vertexStreamZeroSize;\n            uint32_t vertexStreamZeroStride;\n            CsdFilterState csdFilterState;\n        } drawPrimitiveUP;\n\n        struct \n        {\n            GuestVertexDeclaration* vertexDeclaration;\n        } setVertexDeclaration;\n\n        struct \n        {\n            GuestShader* shader;\n        } setVertexShader;\n\n        struct \n        {\n            uint32_t index;\n            GuestBuffer* buffer;\n            uint32_t offset;\n            uint32_t stride;\n        } setStreamSource;\n\n        struct \n        {\n            GuestBuffer* buffer;\n        } setIndices;\n\n        struct \n        {\n            GuestShader* shader;\n        } setPixelShader;\n\n        struct\n        {\n            bool enabled;\n            uint32_t index;\n        } setConditionalSurvey;\n\n        struct\n        {\n            bool enabled;\n            uint32_t index;\n        } setConditionalRendering;\n    };\n};\n\nstatic moodycamel::BlockingConcurrentQueue<RenderCommand> g_renderQueue;\n\ntemplate<GuestRenderState TType>\nstatic void SetRenderState(GuestDevice* device, uint32_t value)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetRenderState;\n    cmd.setRenderState.type = TType;\n    cmd.setRenderState.value = value;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void SetRenderStateUnimplemented(GuestDevice* device, uint32_t value)\n{\n    LOGF_WARNING(\"{:x}\\n\", value);\n}\n\nstatic void SetAlphaTestMode(bool enable)\n{\n    uint32_t specConstants = 0;\n    bool enableAlphaToCoverage = false;\n\n    if (enable)\n    {\n        enableAlphaToCoverage = Config::TransparencyAntiAliasing && g_renderTarget != nullptr && g_renderTarget->sampleCount != RenderSampleCount::COUNT_1;\n\n        if (enableAlphaToCoverage)\n            specConstants = SPEC_CONSTANT_ALPHA_TO_COVERAGE;\n        else\n            specConstants = SPEC_CONSTANT_ALPHA_TEST;\n    }\n\n    specConstants |= (g_pipelineState.specConstants & ~(SPEC_CONSTANT_ALPHA_TEST | SPEC_CONSTANT_ALPHA_TO_COVERAGE));\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.enableAlphaToCoverage, enableAlphaToCoverage);\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.specConstants, specConstants);\n}\n\nstatic RenderBlend ConvertBlendMode(uint32_t blendMode)\n{\n    switch (blendMode)\n    {\n    case D3DBLEND_ZERO:\n        return RenderBlend::ZERO;\n    case D3DBLEND_ONE:\n        return RenderBlend::ONE;\n    case D3DBLEND_SRCCOLOR:\n        return RenderBlend::SRC_COLOR;\n    case D3DBLEND_INVSRCCOLOR:\n        return RenderBlend::INV_SRC_COLOR;\n    case D3DBLEND_SRCALPHA:\n        return RenderBlend::SRC_ALPHA;\n    case D3DBLEND_INVSRCALPHA:\n        return RenderBlend::INV_SRC_ALPHA;\n    case D3DBLEND_DESTCOLOR:\n        return RenderBlend::DEST_COLOR;\n    case D3DBLEND_INVDESTCOLOR:\n        return RenderBlend::INV_DEST_COLOR;\n    case D3DBLEND_DESTALPHA:\n        return RenderBlend::DEST_ALPHA;\n    case D3DBLEND_INVDESTALPHA:\n        return RenderBlend::INV_DEST_ALPHA;\n    default:\n        assert(false && \"Invalid blend mode\");\n        return RenderBlend::ZERO;\n    }\n}\n\nstatic RenderBlendOperation ConvertBlendOp(uint32_t blendOp)\n{\n    switch (blendOp)\n    {\n    case D3DBLENDOP_ADD:\n        return RenderBlendOperation::ADD;\n    case D3DBLENDOP_SUBTRACT:\n        return RenderBlendOperation::SUBTRACT;\n    case D3DBLENDOP_REVSUBTRACT:\n        return RenderBlendOperation::REV_SUBTRACT;\n    case D3DBLENDOP_MIN:\n        return RenderBlendOperation::MIN;\n    case D3DBLENDOP_MAX:\n        return RenderBlendOperation::MAX;\n    default:\n        assert(false && \"Unknown blend operation\");\n        return RenderBlendOperation::ADD;\n    }\n}\n\nstatic RenderComparisonFunction ConvertCompareFunc(uint32_t compareFunc)\n{\n    switch (compareFunc)\n    {\n    case D3DCMP_NEVER:\n        return RenderComparisonFunction::NEVER;\n    case D3DCMP_LESS:\n        return RenderComparisonFunction::LESS;\n    case D3DCMP_EQUAL:\n        return RenderComparisonFunction::EQUAL;\n    case D3DCMP_LESSEQUAL:\n        return RenderComparisonFunction::LESS_EQUAL;\n    case D3DCMP_GREATER:\n        return RenderComparisonFunction::GREATER;\n    case D3DCMP_NOTEQUAL:\n        return RenderComparisonFunction::NOT_EQUAL;\n    case D3DCMP_GREATEREQUAL:\n        return RenderComparisonFunction::GREATER_EQUAL;\n    case D3DCMP_ALWAYS:\n        return RenderComparisonFunction::ALWAYS;\n    default:\n        assert(false && \"Unknown comparison function\");\n        return RenderComparisonFunction::NEVER;\n    }\n}\n\nstatic RenderStencilOp ConvertStencilOp(uint32_t stencilOp)\n{\n    switch (stencilOp)\n    {\n    case D3DSTENCILOP_KEEP:\n        return RenderStencilOp::KEEP;\n    case D3DSTENCILOP_ZERO:\n        return RenderStencilOp::ZERO;\n    case D3DSTENCILOP_REPLACE:\n        return RenderStencilOp::REPLACE;\n    case D3DSTENCILOP_INCRSAT:\n        return RenderStencilOp::INCREMENT_AND_CLAMP;\n    case D3DSTENCILOP_DECRSAT:\n        return RenderStencilOp::DECREMENT_AND_CLAMP;\n    case D3DSTENCILOP_INVERT:\n        return RenderStencilOp::INVERT;\n    case D3DSTENCILOP_INCR:\n        return RenderStencilOp::INCREMENT_AND_WRAP;\n    case D3DSTENCILOP_DECR:\n        return RenderStencilOp::DECREMENT_AND_WRAP;\n    default:\n        assert(false && \"Unknown stencil op\");\n        return RenderStencilOp::KEEP;\n    }\n}\n\nstatic void ProcSetRenderState(const RenderCommand& cmd)\n{\n    uint32_t value = cmd.setRenderState.value;\n\n    switch (cmd.setRenderState.type)\n    {\n    case D3DRS_ZENABLE:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.zEnable, value != 0);\n        g_dirtyStates.renderTargetAndDepthStencil |= g_dirtyStates.pipelineState;\n        break;\n    }\n    case D3DRS_ZWRITEENABLE:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.zWriteEnable, value != 0);\n        break;\n    }\n    case D3DRS_STENCILENABLE:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilEnable, value != 0);\n        g_dirtyStates.renderTargetAndDepthStencil |= g_dirtyStates.pipelineState;\n        break;\n    }\n    case D3DRS_TWOSIDEDSTENCILMODE:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilTwoSided, value != 0);\n        break;\n    }\n    case D3DRS_ALPHATESTENABLE:\n    {\n        SetAlphaTestMode(value != 0);\n        break;\n    }\n    case D3DRS_SRCBLEND:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.srcBlend, ConvertBlendMode(value));\n        break;\n    }\n    case D3DRS_DESTBLEND:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.destBlend, ConvertBlendMode(value));\n        break;\n    }\n    case D3DRS_CULLMODE:\n    {\n        RenderCullMode cullMode;\n\n        switch (value) {\n        case D3DCULL_NONE_CCW:\n        case D3DCULL_NONE_CW:\n            cullMode = RenderCullMode::NONE;\n            break;\n        case D3DCULL_FRONT_CCW:\n        case D3DCULL_FRONT_CW:\n            cullMode = RenderCullMode::FRONT;\n            break;\n        case D3DCULL_BACK_CCW:\n        case D3DCULL_BACK_CW:\n            cullMode = RenderCullMode::BACK;\n            break;\n        default:\n            assert(false && \"Invalid cull mode\");\n            cullMode = RenderCullMode::NONE;\n            break;\n        }\n\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.cullMode, cullMode);\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.frontFace, value < D3DCULL_NONE_CW ? RenderFrontFace::COUNTER_CLOCKWISE : RenderFrontFace::CLOCKWISE);\n        break;\n    }\n    case D3DRS_ZFUNC:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.zFunc, ConvertCompareFunc(value));\n        break;\n    }\n    case D3DRS_STENCILFUNC:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilFunc, ConvertCompareFunc(value));\n        break;\n    }\n    case D3DRS_STENCILFAIL:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilFail, ConvertStencilOp(value));\n        break;\n    }\n    case D3DRS_STENCILZFAIL:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilZFail, ConvertStencilOp(value));\n        break;\n    }\n    case D3DRS_STENCILPASS:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilPass, ConvertStencilOp(value));\n        break;\n    }\n    case D3DRS_CCW_STENCILFUNC:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilFuncCCW, ConvertCompareFunc(value));\n        break;\n    }\n    case D3DRS_CCW_STENCILFAIL:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilFailCCW, ConvertStencilOp(value));\n        break;\n    }\n    case D3DRS_CCW_STENCILZFAIL:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilZFailCCW, ConvertStencilOp(value));\n        break;\n    }\n    case D3DRS_CCW_STENCILPASS:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilPassCCW, ConvertStencilOp(value));\n        break;\n    }\n    case D3DRS_STENCILREF:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilRef, value);\n        break;\n    }\n    case D3DRS_STENCILMASK:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilMask, value);\n        break;\n    }\n    case D3DRS_STENCILWRITEMASK:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.stencilWriteMask, value);\n        break;\n    }\n    case D3DRS_ALPHAREF:\n    {\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.alphaThreshold, float(value) / 256.0f);\n        break;\n    }\n    case D3DRS_ALPHABLENDENABLE:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.alphaBlendEnable, value != 0);\n        break;\n    }\n    case D3DRS_BLENDOP:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.blendOp, ConvertBlendOp(value));\n        break;\n    }\n    case D3DRS_SCISSORTESTENABLE:\n    {\n        // HACK: Ignore scissor test on depth-only draws to allow CSM:3 to properly scale\n        if (g_pipelineState.depthStencilFormat == RenderFormat::UNKNOWN || g_pipelineState.renderTargetFormat != RenderFormat::UNKNOWN)\n            SetDirtyValue(g_dirtyStates.scissorRect, g_scissorTestEnable, value != 0);\n        break;\n    }\n    case D3DRS_SLOPESCALEDEPTHBIAS:\n    {\n        if (g_capabilities.dynamicDepthBias)\n            SetDirtyValue(g_dirtyStates.depthBias, g_slopeScaledDepthBias, *reinterpret_cast<float*>(&value));\n        else \n            SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.slopeScaledDepthBias, *reinterpret_cast<float*>(&value));\n\n        break;\n    }\n    case D3DRS_DEPTHBIAS:\n    {\n        if (g_capabilities.dynamicDepthBias)\n            SetDirtyValue(g_dirtyStates.depthBias, g_depthBias, int32_t(*reinterpret_cast<float*>(&value) * (1 << 24)));\n        else\n            SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.depthBias, int32_t(*reinterpret_cast<float*>(&value)* (1 << 24)));\n\n        break;\n    }\n    case D3DRS_SRCBLENDALPHA:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.srcBlendAlpha, ConvertBlendMode(value));\n        break;\n    }\n    case D3DRS_DESTBLENDALPHA:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.destBlendAlpha, ConvertBlendMode(value));\n        break;\n    }\n    case D3DRS_BLENDOPALPHA:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.blendOpAlpha, ConvertBlendOp(value));\n        break;\n    }\n    case D3DRS_COLORWRITEENABLE:\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.colorWriteEnable, value);\n        g_dirtyStates.renderTargetAndDepthStencil |= g_dirtyStates.pipelineState;\n        break;\n    }\n    case D3DRS_CLIPPLANEENABLE:\n    {\n        // HACK: Only check for clip pane 0\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.clipPlaneEnabled, (value & 1) == 1);\n    }\n    }\n}\n\nstatic const std::pair<GuestRenderState, PPCFunc*> g_setRenderStateFunctions[] =\n{\n    { D3DRS_ZENABLE, HostToGuestFunction<SetRenderState<D3DRS_ZENABLE>> },\n    { D3DRS_ZWRITEENABLE, HostToGuestFunction<SetRenderState<D3DRS_ZWRITEENABLE>> },\n    { D3DRS_ALPHATESTENABLE, HostToGuestFunction<SetRenderState<D3DRS_ALPHATESTENABLE>> },\n    { D3DRS_SRCBLEND, HostToGuestFunction<SetRenderState<D3DRS_SRCBLEND>> },\n    { D3DRS_DESTBLEND, HostToGuestFunction<SetRenderState<D3DRS_DESTBLEND>> },\n    { D3DRS_CULLMODE, HostToGuestFunction<SetRenderState<D3DRS_CULLMODE>> },\n    { D3DRS_ZFUNC, HostToGuestFunction<SetRenderState<D3DRS_ZFUNC>> },\n    { D3DRS_ALPHAREF, HostToGuestFunction<SetRenderState<D3DRS_ALPHAREF>> },\n    { D3DRS_ALPHABLENDENABLE, HostToGuestFunction<SetRenderState<D3DRS_ALPHABLENDENABLE>> },\n    { D3DRS_BLENDOP, HostToGuestFunction<SetRenderState<D3DRS_BLENDOP>> },\n    { D3DRS_SCISSORTESTENABLE, HostToGuestFunction<SetRenderState<D3DRS_SCISSORTESTENABLE>> },\n    { D3DRS_SLOPESCALEDEPTHBIAS, HostToGuestFunction<SetRenderState<D3DRS_SLOPESCALEDEPTHBIAS>> },\n    { D3DRS_DEPTHBIAS, HostToGuestFunction<SetRenderState<D3DRS_DEPTHBIAS>> },\n    { D3DRS_SRCBLENDALPHA, HostToGuestFunction<SetRenderState<D3DRS_SRCBLENDALPHA>> },\n    { D3DRS_DESTBLENDALPHA, HostToGuestFunction<SetRenderState<D3DRS_DESTBLENDALPHA>> },\n    { D3DRS_BLENDOPALPHA, HostToGuestFunction<SetRenderState<D3DRS_BLENDOPALPHA>> },\n    { D3DRS_COLORWRITEENABLE, HostToGuestFunction<SetRenderState<D3DRS_COLORWRITEENABLE>> },\n    { D3DRS_STENCILENABLE, HostToGuestFunction<SetRenderState<D3DRS_STENCILENABLE>> },\n    { D3DRS_TWOSIDEDSTENCILMODE, HostToGuestFunction<SetRenderState<D3DRS_TWOSIDEDSTENCILMODE>> },\n    { D3DRS_STENCILFAIL, HostToGuestFunction<SetRenderState<D3DRS_STENCILFAIL>> },\n    { D3DRS_STENCILZFAIL, HostToGuestFunction<SetRenderState<D3DRS_STENCILZFAIL>> },\n    { D3DRS_STENCILPASS, HostToGuestFunction<SetRenderState<D3DRS_STENCILPASS>> },\n    { D3DRS_STENCILFUNC, HostToGuestFunction<SetRenderState<D3DRS_STENCILFUNC>> },\n    { D3DRS_STENCILREF, HostToGuestFunction<SetRenderState<D3DRS_STENCILREF>> },\n    { D3DRS_STENCILMASK, HostToGuestFunction<SetRenderState<D3DRS_STENCILMASK>> },\n    { D3DRS_STENCILWRITEMASK, HostToGuestFunction<SetRenderState<D3DRS_STENCILWRITEMASK>> },\n    { D3DRS_CCW_STENCILFAIL, HostToGuestFunction<SetRenderState<D3DRS_CCW_STENCILFAIL>> },\n    { D3DRS_CCW_STENCILZFAIL, HostToGuestFunction<SetRenderState<D3DRS_CCW_STENCILZFAIL>> },\n    { D3DRS_CCW_STENCILPASS, HostToGuestFunction<SetRenderState<D3DRS_CCW_STENCILPASS>> },\n    { D3DRS_CCW_STENCILFUNC, HostToGuestFunction<SetRenderState<D3DRS_CCW_STENCILFUNC>> },\n    { D3DRS_CLIPPLANEENABLE, HostToGuestFunction<SetRenderState<D3DRS_CLIPPLANEENABLE>> }\n};\n\nstatic std::unique_ptr<RenderShader> g_copyShader;\n\nstatic std::unique_ptr<RenderShader> g_copyColorShader;\nstatic ankerl::unordered_dense::map<RenderFormat, std::unique_ptr<RenderPipeline>> g_copyColorPipelines;\nstatic std::unique_ptr<RenderPipeline> g_copyDepthPipeline;\n\nstatic std::unique_ptr<RenderShader> g_resolveMsaaColorShaders[3];\nstatic ankerl::unordered_dense::map<RenderFormat, std::array<std::unique_ptr<RenderPipeline>, 3>> g_resolveMsaaColorPipelines;\nstatic std::unique_ptr<RenderPipeline> g_resolveMsaaDepthPipelines[3];\n\nenum\n{\n    GAUSSIAN_BLUR_3X3,\n    GAUSSIAN_BLUR_5X5,\n    GAUSSIAN_BLUR_7X7,\n    GAUSSIAN_BLUR_9X9,\n    GAUSSIAN_BLUR_COUNT\n};\n\nstatic std::unique_ptr<GuestShader> g_gaussianBlurShaders[GAUSSIAN_BLUR_COUNT];\n\nstatic std::unique_ptr<GuestShader> g_csdFilterShader;\nstatic GuestShader* g_csdShader;\n\nstatic std::unique_ptr<GuestShader> g_enhancedBurnoutBlurVSShader;\nstatic std::unique_ptr<GuestShader> g_enhancedBurnoutBlurPSShader;\n\nstatic std::unique_ptr<GuestShader> g_conditionalSurveyPSShader;\n\n#if defined(MARATHON_RECOMP_D3D12)\n\n#define CREATE_SHADER(NAME) \\\n    g_device->createShader( \\\n        (g_backend == Backend::VULKAN) ? g_##NAME##_spirv : g_##NAME##_dxil, \\\n        (g_backend == Backend::VULKAN) ? sizeof(g_##NAME##_spirv) : sizeof(g_##NAME##_dxil), \\\n        \"shaderMain\", \\\n        (g_backend == Backend::VULKAN) ? RenderShaderFormat::SPIRV : RenderShaderFormat::DXIL)\n\n#elif defined(MARATHON_RECOMP_METAL)\n\n#define CREATE_SHADER(NAME) \\\n    g_device->createShader( \\\n        (g_backend == Backend::VULKAN) ? g_##NAME##_spirv : g_##NAME##_air, \\\n        (g_backend == Backend::VULKAN) ? sizeof(g_##NAME##_spirv) : sizeof(g_##NAME##_air), \\\n        \"shaderMain\", \\\n        (g_backend == Backend::VULKAN) ? RenderShaderFormat::SPIRV : RenderShaderFormat::METAL)\n\n#else\n\n#define CREATE_SHADER(NAME) \\\n    g_device->createShader(g_##NAME##_spirv, sizeof(g_##NAME##_spirv), \"shaderMain\", RenderShaderFormat::SPIRV)\n\n#endif\n\n#ifdef _WIN32\nstatic bool DetectWine()\n{\n    HMODULE dllHandle = GetModuleHandle(\"ntdll.dll\");\n    return dllHandle != nullptr && GetProcAddress(dllHandle, \"wine_get_version\") != nullptr;\n}\n#endif\n\nstatic constexpr size_t TEXTURE_DESCRIPTOR_SIZE = 32768;\nstatic constexpr size_t SAMPLER_DESCRIPTOR_SIZE = 1024;\n\nstatic std::unique_ptr<GuestTexture> g_imFontTexture;\nstatic std::unique_ptr<RenderPipelineLayout> g_imPipelineLayout;\nstatic std::unique_ptr<RenderPipeline> g_imPipeline;\nstatic std::unique_ptr<RenderPipeline> g_imAdditivePipeline;\n\ntemplate<typename T>\nstatic void ExecuteCopyCommandList(const T& function)\n{\n    std::lock_guard lock(g_copyMutex);\n\n    g_copyCommandList->begin();\n    function();\n    g_copyCommandList->end();\n    g_copyQueue->executeCommandLists(g_copyCommandList.get(), g_copyCommandFence.get());\n    g_copyQueue->waitForCommandFence(g_copyCommandFence.get());\n}\n\nstatic constexpr uint32_t PITCH_ALIGNMENT = 0x100;\nstatic constexpr uint32_t PLACEMENT_ALIGNMENT = 0x200;\n\nstruct ImGuiPushConstants\n{\n    ImVec2 boundsMin{};\n    ImVec2 boundsMax{};\n    ImU32 gradientTopLeft{};\n    ImU32 gradientTopRight{};\n    ImU32 gradientBottomRight{};\n    ImU32 gradientBottomLeft{};\n    uint32_t shaderModifier{};\n    uint32_t texture2DDescriptorIndex{};\n    ImVec2 displaySize{};\n    ImVec2 inverseDisplaySize{};\n    ImVec2 origin{ 0.0f, 0.0f };\n    ImVec2 scale{ 1.0f, 1.0f };\n    ImVec2 proceduralOrigin{ 0.0f, 0.0f };\n    float outline{};\n};\n\nextern ImFontBuilderIO g_fontBuilderIO;\n\nstatic void CreateImGuiBackend()\n{\n    ImGuiIO& io = ImGui::GetIO();\n    io.IniFilename = nullptr;\n    io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;\n    io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;\n\n#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT\n    IM_DELETE(io.Fonts);\n    io.Fonts = ImFontAtlasSnapshot::Load();\n#else\n    io.Fonts->AddFontDefault();\n    ImFontAtlasSnapshot::GenerateGlyphRanges();\n#endif\n\n    InitImGuiUtils();\n    OptionsMenu::Init();\n    InstallerWizard::Init();\n\n    ImGui_ImplSDL2_InitForOther(GameWindow::s_pWindow);\n\n#ifdef ENABLE_IM_FONT_ATLAS_SNAPSHOT\n    g_imFontTexture = LoadTexture(\n        decompressZstd(g_im_font_atlas_texture, g_im_font_atlas_texture_uncompressed_size).get(), g_im_font_atlas_texture_uncompressed_size);\n#else\n    io.Fonts->FontBuilderIO = &g_fontBuilderIO;\n    io.Fonts->Build();\n\n    g_imFontTexture = std::make_unique<GuestTexture>(ResourceType::Texture);\n\n    uint8_t* pixels;\n    int width, height;\n    io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);\n\n    RenderTextureDesc textureDesc;\n    textureDesc.dimension = RenderTextureDimension::TEXTURE_2D;\n    textureDesc.width = width;\n    textureDesc.height = height;\n    textureDesc.depth = 1;\n    textureDesc.mipLevels = 1;\n    textureDesc.arraySize = 1;\n    textureDesc.format = RenderFormat::R8G8B8A8_UNORM;\n\n    g_imFontTexture->textureHolder = g_device->createTexture(textureDesc);\n    g_imFontTexture->texture = g_imFontTexture->textureHolder.get();\n\n    uint32_t rowPitch = (width * 4 + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1);\n    uint32_t slicePitch = (rowPitch * height + PLACEMENT_ALIGNMENT - 1) & ~(PLACEMENT_ALIGNMENT - 1);\n    auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(slicePitch));\n    uint8_t* mappedMemory = reinterpret_cast<uint8_t*>(uploadBuffer->map());\n\n    if (rowPitch == (width * 4))\n    {\n        memcpy(mappedMemory, pixels, slicePitch);\n    }\n    else\n    {\n        for (size_t i = 0; i < height; i++)\n        {\n            memcpy(mappedMemory, pixels, width * 4);\n            pixels += width * 4;\n            mappedMemory += rowPitch;\n        }\n    }\n\n    uploadBuffer->unmap();\n\n    ExecuteCopyCommandList([&]\n        {\n            g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(g_imFontTexture->texture, RenderTextureLayout::COPY_DEST));\n\n            g_copyCommandList->copyTextureRegion(\n                RenderTextureCopyLocation::Subresource(g_imFontTexture->texture, 0),\n                RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0));\n        });\n\n    g_imFontTexture->layout = RenderTextureLayout::COPY_DEST;\n\n    RenderTextureViewDesc textureViewDesc;\n    textureViewDesc.format = textureDesc.format;\n    textureViewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D;\n    textureViewDesc.mipLevels = 1;\n    g_imFontTexture->textureView = g_imFontTexture->texture->createTextureView(textureViewDesc);\n\n    g_imFontTexture->descriptorIndex = g_textureDescriptorAllocator.allocate();\n    g_textureDescriptorSet->setTexture(g_imFontTexture->descriptorIndex, g_imFontTexture->texture, RenderTextureLayout::SHADER_READ, g_imFontTexture->textureView.get());\n#endif\n\n    io.Fonts->SetTexID(g_imFontTexture.get());\n\n    RenderPipelineLayoutBuilder pipelineLayoutBuilder;\n    pipelineLayoutBuilder.begin(false, true);\n\n    RenderDescriptorSetBuilder descriptorSetBuilder;\n    descriptorSetBuilder.begin();\n    descriptorSetBuilder.addTexture(0, TEXTURE_DESCRIPTOR_SIZE);\n    descriptorSetBuilder.end(true, TEXTURE_DESCRIPTOR_SIZE);\n    pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);\n\n    descriptorSetBuilder.begin();\n    descriptorSetBuilder.addSampler(0, SAMPLER_DESCRIPTOR_SIZE);\n    descriptorSetBuilder.end(true, SAMPLER_DESCRIPTOR_SIZE);\n    pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);\n\n    pipelineLayoutBuilder.addPushConstant(0, 2, sizeof(ImGuiPushConstants), RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL);\n\n    pipelineLayoutBuilder.end();\n    g_imPipelineLayout = pipelineLayoutBuilder.create(g_device.get());\n\n    auto vertexShader = CREATE_SHADER(imgui_vs);\n    auto pixelShader = CREATE_SHADER(imgui_ps);\n\n    RenderInputElement inputElements[3];\n    inputElements[0] = RenderInputElement(\"POSITION\", 0, 0, RenderFormat::R32G32_FLOAT, 0, offsetof(ImDrawVert, pos));\n    inputElements[1] = RenderInputElement(\"TEXCOORD\", 0, 1, RenderFormat::R32G32_FLOAT, 0, offsetof(ImDrawVert, uv));\n    inputElements[2] = RenderInputElement(\"COLOR\", 0, 2, RenderFormat::R8G8B8A8_UNORM, 0, offsetof(ImDrawVert, col));\n\n    RenderInputSlot inputSlot(0, sizeof(ImDrawVert));\n\n    RenderGraphicsPipelineDesc pipelineDesc;\n    pipelineDesc.pipelineLayout = g_imPipelineLayout.get();\n    pipelineDesc.vertexShader = vertexShader.get();\n    pipelineDesc.pixelShader = pixelShader.get();\n    pipelineDesc.renderTargetFormat[0] = BACKBUFFER_FORMAT;\n    pipelineDesc.renderTargetBlend[0] = RenderBlendDesc::AlphaBlend();\n    pipelineDesc.renderTargetCount = 1;\n    pipelineDesc.inputElements = inputElements;\n    pipelineDesc.inputElementsCount = std::size(inputElements);\n    pipelineDesc.inputSlots = &inputSlot;\n    pipelineDesc.inputSlotsCount = 1;\n    g_imPipeline = g_device->createGraphicsPipeline(pipelineDesc);\n\n    pipelineDesc.renderTargetBlend[0].dstBlend = RenderBlend::ONE;\n    g_imAdditivePipeline = g_device->createGraphicsPipeline(pipelineDesc);\n\n#ifndef ENABLE_IM_FONT_ATLAS_SNAPSHOT\n    ImFontAtlasSnapshot snapshot;\n    snapshot.Snap();\n\n    FILE* file = fopen(\"im_font_atlas.bin\", \"wb\");\n    if (file)\n    {\n        fwrite(snapshot.data.data(), 1, snapshot.data.size(), file);\n        fclose(file);\n    }\n\n    ddspp::Header header;\n    ddspp::HeaderDXT10 headerDX10;\n    ddspp::encode_header(ddspp::R8G8B8A8_UNORM, width, height, 1, ddspp::Texture2D, 1, 1, header, headerDX10);\n\n    file = fopen(\"im_font_atlas.dds\", \"wb\");\n    if (file)\n    {\n        fwrite(&ddspp::DDS_MAGIC, 4, 1, file);\n        fwrite(&header, sizeof(header), 1, file);\n        fwrite(&headerDX10, sizeof(headerDX10), 1, file);\n        fwrite(pixels, 4, width * height, file);\n        fclose(file);\n    }\n#endif\n}\n\nstatic void CheckSwapChain()\n{\n    g_swapChain->setVsyncEnabled(Config::VSync);\n    g_swapChainValid &= !g_swapChain->needsResize();\n\n    if (!g_swapChainValid)\n    {\n        Video::WaitForGPU();\n        g_backBuffer->framebuffers.clear();\n        g_swapChainValid = g_swapChain->resize();\n        g_needsResize = g_swapChainValid;\n    }\n\n    if (g_swapChainValid)\n    {\n        g_swapChainAcquireProfiler.Begin();\n        g_swapChainValid = g_swapChain->acquireTexture(g_acquireSemaphores[g_frame].get(), &g_backBufferIndex);\n        g_swapChainAcquireProfiler.End();\n    }\n\n    if (g_needsResize)\n        Video::ComputeViewportDimensions();\n\n    g_backBuffer->width = Video::s_viewportWidth;\n    g_backBuffer->height = Video::s_viewportHeight;\n}\n\nstatic void BeginCommandList()\n{\n    g_renderTarget = g_backBuffer;\n    g_depthStencil = nullptr;\n    g_framebuffer = nullptr;\n\n    g_pipelineState.renderTargetFormat = BACKBUFFER_FORMAT;\n    g_pipelineState.depthStencilFormat = RenderFormat::UNKNOWN;\n\n    if (g_swapChainValid)\n    {\n        uint32_t width = Video::s_viewportWidth;\n        uint32_t height = Video::s_viewportHeight;\n\n        if (g_intermediaryBackBufferTextureWidth != width ||\n            g_intermediaryBackBufferTextureHeight != height)\n        {\n            if (g_intermediaryBackBufferTextureDescriptorIndex == NULL)\n                g_intermediaryBackBufferTextureDescriptorIndex = g_textureDescriptorAllocator.allocate();\n\n            Video::WaitForGPU(); // Fine to wait for GPU, this'll only happen during resize.\n\n            g_intermediaryBackBufferTexture = g_device->createTexture(RenderTextureDesc::Texture2D(width, height, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));\n            g_textureDescriptorSet->setTexture(g_intermediaryBackBufferTextureDescriptorIndex, g_intermediaryBackBufferTexture.get(), RenderTextureLayout::SHADER_READ);\n\n            g_intermediaryBackBufferTextureWidth = width;\n            g_intermediaryBackBufferTextureHeight = height;\n\n            g_backBuffer->framebuffers.clear();\n        }\n\n        g_backBuffer->texture = g_intermediaryBackBufferTexture.get();\n    }\n    else\n    {\n        g_backBuffer->texture = g_backBuffer->textureHolder.get();\n    }\n\n    g_backBuffer->layout = RenderTextureLayout::UNKNOWN;\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        g_sharedConstants.texture2DIndices[i] = TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D;\n        g_sharedConstants.texture2DArrayIndices[i] = TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY;\n        g_sharedConstants.textureCubeIndices[i] = TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE;\n    }\n\n    memset(g_textures, 0, sizeof(g_textures));\n\n    auto& commandList = g_commandLists[g_frame];\n\n    commandList->begin();\n    commandList->resetQueryPool(g_queryPools[g_frame].get(), 0, NUM_QUERIES);\n    commandList->writeTimestamp(g_queryPools[g_frame].get(), 0);\n    commandList->setGraphicsPipelineLayout(g_pipelineLayout.get());\n    commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 0);\n    commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 1);\n    commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 2);\n    commandList->setGraphicsDescriptorSet(g_samplerDescriptorSet.get(), 3);\n    commandList->setGraphicsDescriptorSet(g_conditionalSurveyDescriptorSet.get(), 4);\n\n    g_readyForCommands = true;\n    g_readyForCommands.notify_one();\n}\n\ntemplate<typename T>\nstatic void ApplyLowEndDefault(ConfigDef<T> &configDef, T newDefault, bool &changed)\n{\n    if (configDef.IsDefaultValue() && !configDef.IsLoadedFromConfig)\n    {\n        configDef = newDefault;\n        changed = true;\n    }\n    \n    configDef.DefaultValue = newDefault;\n}\n\nstatic void ApplyLowEndDefaults()\n{\n    bool changed = false;\n\n    ApplyLowEndDefault(Config::AntiAliasing, EAntiAliasing::MSAA2x, changed);\n    ApplyLowEndDefault(Config::ShadowResolution, EShadowResolution::x1024, changed);\n    ApplyLowEndDefault(Config::ReflectionResolution, EReflectionResolution::Quarter, changed);\n    ApplyLowEndDefault(Config::TransparencyAntiAliasing, false, changed);\n\n    if (changed) \n    {\n        Config::Save();\n    }\n}\n\nbool Video::CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry)\n{\n    for (uint32_t i = 0; i < 16; i++)\n        g_inputSlots[i].index = i;\n\n    IMGUI_CHECKVERSION();\n    ImGui::CreateContext();\n    ImPlot::CreateContext();\n\n    GameWindow::Init(sdlVideoDriver);\n\n#if defined(MARATHON_RECOMP_D3D12)\n    g_backend = (DetectWine() || Config::GraphicsAPI == EGraphicsAPI::Vulkan) ? Backend::VULKAN : Backend::D3D12;\n#elif defined(MARATHON_RECOMP_METAL)\n    g_backend = Config::GraphicsAPI == EGraphicsAPI::Vulkan ? Backend::VULKAN : Backend::METAL;\n#endif\n\n    // Attempt to create the possible backends using a vector of function pointers. Whichever succeeds first will be the chosen API.\n    using RenderInterfaceFunction = std::unique_ptr<RenderInterface>(void);\n    std::vector<RenderInterfaceFunction *> interfaceFunctions;\n\n#ifdef MARATHON_RECOMP_D3D12\n    bool allowVulkanRedirection = true;\n\n    if (graphicsApiRetry)\n    {\n        // If we are attempting to create again after a reboot due to a crash, swap the order.\n        g_backend = (g_backend == Backend::VULKAN) ? Backend::D3D12 : Backend::VULKAN;\n\n        // Don't allow redirection to Vulkan if we are retrying after a crash, \n        // so the user can at least boot the game with D3D12 if Vulkan fails to work.\n        allowVulkanRedirection = false;\n    }\n\n    interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateVulkanInterfaceWrapper : CreateD3D12Interface);\n    interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateD3D12Interface : CreateVulkanInterfaceWrapper);\n#elif defined(MARATHON_RECOMP_METAL)\n    interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateVulkanInterfaceWrapper : CreateMetalInterface);\n    interfaceFunctions.push_back((g_backend == Backend::VULKAN) ? CreateMetalInterface : CreateVulkanInterfaceWrapper);\n#else\n    interfaceFunctions.push_back(CreateVulkanInterfaceWrapper);\n#endif\n\n    for (size_t i = 0; i < interfaceFunctions.size(); i++)\n    {\n        RenderInterfaceFunction* interfaceFunction = interfaceFunctions[i];\n\n#ifdef MARATHON_RECOMP_D3D12\n        // Wrap the device creation in __try/__except to survive from driver crashes.\n        __try\n#endif\n        {\n            g_interface = interfaceFunction();\n            if (g_interface == nullptr)\n            {\n                continue;\n            }\n\n            g_device = g_interface->createDevice(Config::GraphicsDevice);\n            if (g_device != nullptr)\n            {\n                const RenderDeviceDescription &deviceDescription = g_device->getDescription();\n                \n#if defined(MARATHON_RECOMP_D3D12)\n                if (interfaceFunction == CreateD3D12Interface)\n                {\n                    if (allowVulkanRedirection)\n                    {\n                        bool redirectToVulkan = false;\n\n                        // ...\n                        // There used to be driver redirections here, but they are all free from Vulkan purgatory for now...\n                        // ...\n\n                        if (redirectToVulkan)\n                        {\n                            g_device.reset();\n                            g_interface.reset();\n\n                            // In case Vulkan fails to initialize, we will try D3D12 again afterwards, \n                            // just to get the game to boot. This only really happens in very old Intel GPU drivers.\n                            if (g_backend != Backend::VULKAN)\n                            {\n                                interfaceFunctions.push_back(CreateD3D12Interface);\n                                allowVulkanRedirection = false;\n                            }\n\n                            continue;\n                        }\n                    }\n                }\n\n                g_backend = (interfaceFunction == CreateVulkanInterfaceWrapper) ? Backend::VULKAN : Backend::D3D12;\n#elif defined(MARATHON_RECOMP_METAL)\n                g_backend = (interfaceFunction == CreateVulkanInterfaceWrapper) ? Backend::VULKAN : Backend::METAL;\n#endif\n                // Enable triangle strip workaround if we are on AMD, as there is a bug where\n                // restart indices cause triangles to be culled incorrectly. Converting them to degenerate triangles fixes it.\n                g_triangleStripWorkaround = (deviceDescription.vendor == RenderDeviceVendor::AMD);\n\n                break;\n            }\n        }\n#ifdef MARATHON_RECOMP_D3D12\n        __except (EXCEPTION_EXECUTE_HANDLER)\n        {\n            if (graphicsApiRetry)\n            {\n                // If we were retrying, and this also failed, then we'll show the user neither of the graphics APIs succeeded.\n                return false;\n            }\n            else\n            {\n                // If this is the first crash we ran into, reboot and try the other graphics API.\n                os::process::StartProcess(os::process::GetExecutablePath(), { \"--graphics-api-retry\" });\n                std::_Exit(0);\n            }\n        }\n#endif\n    }\n\n    if (g_device == nullptr)\n    {\n        return false;\n    }\n\n#ifdef MARATHON_RECOMP_D3D12\n    if (graphicsApiRetry)\n    {\n        // If we managed to create a device after retrying it in a reboot, remember the one we picked.\n        Config::GraphicsAPI = g_backend == Backend::VULKAN ? EGraphicsAPI::Vulkan : EGraphicsAPI::D3D12;\n    }\n#endif\n\n    g_capabilities = g_device->getCapabilities();\n\n    LoadEmbeddedResources();\n\n    constexpr uint64_t LowEndMemoryLimit = 2048ULL * 1024ULL * 1024ULL;\n    RenderDeviceDescription deviceDescription = g_device->getDescription();\n    bool lowEndType = deviceDescription.type != RenderDeviceType::UNKNOWN && deviceDescription.type != RenderDeviceType::DISCRETE;\n    bool lowEndMemory = deviceDescription.dedicatedVideoMemory < LowEndMemoryLimit;\n    bool lowEndUMA = deviceDescription.type == RenderDeviceType::UNKNOWN && g_capabilities.uma;\n    if (lowEndType || lowEndMemory || lowEndUMA)\n    {\n        // Switch to low end defaults if a non-discrete GPU was detected or a low amount of VRAM was detected.\n        // Checking for UMA on D3D12 seems to be a reliable way to detect integrated GPUs.\n        ApplyLowEndDefaults();\n    }\n\n    const RenderSampleCounts colourSampleCount = g_device->getSampleCountsSupported(RenderFormat::R16G16B16A16_FLOAT);\n    const RenderSampleCounts depthSampleCount  = g_device->getSampleCountsSupported(RenderFormat::D32_FLOAT);\n    const RenderSampleCounts commonSampleCount = colourSampleCount & depthSampleCount;\n\n    // Disable specific MSAA levels if they are not supported.\n    if ((commonSampleCount & RenderSampleCount::COUNT_2) == 0)\n        Config::AntiAliasing.InaccessibleValues.emplace(EAntiAliasing::MSAA2x);\n    if ((commonSampleCount & RenderSampleCount::COUNT_4) == 0)\n        Config::AntiAliasing.InaccessibleValues.emplace(EAntiAliasing::MSAA4x);\n    if ((commonSampleCount & RenderSampleCount::COUNT_8) == 0)\n        Config::AntiAliasing.InaccessibleValues.emplace(EAntiAliasing::MSAA8x);\n\n    // Set Anti-Aliasing to nearest supported level.\n    Config::AntiAliasing.SnapToNearestAccessibleValue(false);\n\n    g_queue = g_device->createCommandQueue(RenderCommandListType::DIRECT);\n\n    for (auto& commandList : g_commandLists)\n        commandList = g_queue->createCommandList();\n\n    for (auto& commandFence : g_commandFences)\n        commandFence = g_device->createCommandFence();\n\n    for (auto& queryPool : g_queryPools)\n        queryPool = g_device->createQueryPool(NUM_QUERIES);\n\n    g_copyQueue = g_device->createCommandQueue(RenderCommandListType::COPY);\n    g_copyCommandList = g_copyQueue->createCommandList();\n    g_copyCommandFence = g_device->createCommandFence();\n\n    if (g_backend == Backend::D3D12)\n    {\n        g_discardCommandList = g_queue->createCommandList();\n        g_discardCommandFence = g_device->createCommandFence();\n    }\n\n    uint32_t bufferCount = 2;\n\n    switch (Config::TripleBuffering)\n    {\n    case ETripleBuffering::Auto:\n        switch (g_backend) {\n        case Backend::VULKAN:\n            // Defaulting to 3 is fine if presentWait as supported, as the maximum frame latency allowed is only 1.\n            bufferCount = g_device->getCapabilities().presentWait ? 3 : 2;\n            break;\n        case Backend::D3D12:\n            // Defaulting to 3 is fine on D3D12 thanks to flip discard model.\n            bufferCount = 3;\n            break;\n        case Backend::METAL:\n            bufferCount = 2;\n            break;\n        }\n\n        break;\n    case ETripleBuffering::On:\n        bufferCount = 3;\n        break;\n    case ETripleBuffering::Off:\n        bufferCount = 2;\n        break;\n    }\n\n    RenderSwapChainDesc swapChainDesc;\n    swapChainDesc.renderWindow = GameWindow::s_renderWindow;\n    swapChainDesc.textureCount = bufferCount;\n    swapChainDesc.format = BACKBUFFER_FORMAT;\n    swapChainDesc.maxFrameLatency = Config::MaxFrameLatency;\n    swapChainDesc.enablePresentWait = g_capabilities.presentWait;\n\n    g_swapChain = g_queue->createSwapChain(swapChainDesc);\n    g_swapChain->setVsyncEnabled(Config::VSync);\n    g_swapChainValid = !g_swapChain->needsResize();\n\n    for (auto& acquireSemaphore : g_acquireSemaphores)\n        acquireSemaphore = g_device->createCommandSemaphore();\n    \n    for (auto& renderSemaphore : g_renderSemaphores)\n        renderSemaphore = g_device->createCommandSemaphore();\n\n    RenderPipelineLayoutBuilder pipelineLayoutBuilder;\n    pipelineLayoutBuilder.begin(false, true);\n    \n    RenderDescriptorSetBuilder descriptorSetBuilder;\n    descriptorSetBuilder.begin();\n    descriptorSetBuilder.addTexture(0, TEXTURE_DESCRIPTOR_SIZE);\n    descriptorSetBuilder.end(true, TEXTURE_DESCRIPTOR_SIZE);\n    \n    g_textureDescriptorSet = descriptorSetBuilder.create(g_device.get());\n    \n    for (size_t i = 0; i < TEXTURE_DESCRIPTOR_NULL_COUNT; i++)\n    {\n        auto& texture = g_blankTextures[i];\n        auto& textureView = g_blankTextureViews[i];\n\n        RenderTextureDesc desc;\n        desc.width = 1;\n        desc.height = 1;\n        desc.depth = 1;\n        desc.mipLevels = 1;\n        desc.format = RenderFormat::R8_UNORM;\n\n        RenderTextureViewDesc viewDesc;\n        viewDesc.format = desc.format;\n        viewDesc.componentMapping = RenderComponentMapping(RenderSwizzle::ZERO, RenderSwizzle::ZERO, RenderSwizzle::ZERO, RenderSwizzle::ZERO);\n        viewDesc.mipLevels = 1;\n\n        switch (i)\n        {\n        case TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D:\n            desc.dimension = RenderTextureDimension::TEXTURE_2D;\n            desc.arraySize = 1;\n            viewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D;\n            break;\n\n        case TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY:\n            desc.dimension = RenderTextureDimension::TEXTURE_2D;\n            desc.arraySize = 1;\n            viewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D;\n            break;\n\n        case TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE:\n            desc.dimension = RenderTextureDimension::TEXTURE_2D;\n            desc.arraySize = 6;\n            desc.flags = RenderTextureFlag::CUBE;\n            viewDesc.dimension = RenderTextureViewDimension::TEXTURE_CUBE;\n            break;\n\n        default:\n            assert(false && \"Unknown null descriptor dimension\");\n            break;\n        }\n\n        texture = g_device->createTexture(desc);\n        textureView = texture->createTextureView(viewDesc);\n\n        g_textureDescriptorSet->setTexture(i, texture.get(), RenderTextureLayout::SHADER_READ, textureView.get());\n    }\n\n    pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);\n    pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);\n    pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);\n    \n    descriptorSetBuilder.begin();\n    descriptorSetBuilder.addSampler(0, SAMPLER_DESCRIPTOR_SIZE);\n    descriptorSetBuilder.end(true, SAMPLER_DESCRIPTOR_SIZE);\n    \n    g_samplerDescriptorSet = descriptorSetBuilder.create(g_device.get());\n    auto& [descriptorIndex, sampler] = g_samplerStates[XXH3_64bits(&g_samplerDescs[0], sizeof(RenderSamplerDesc))];\n    descriptorIndex = 1;\n    sampler = g_device->createSampler(g_samplerDescs[0]);\n    g_samplerDescriptorSet->setSampler(0, sampler.get());\n\n    pipelineLayoutBuilder.addDescriptorSet(descriptorSetBuilder);\n\n    RenderBufferDesc conditionalSurveyBufferDesc;\n    conditionalSurveyBufferDesc.size = CONDITIONAL_SURVEY_MAX * sizeof(uint32_t);\n    conditionalSurveyBufferDesc.heapType = RenderHeapType::DEFAULT;\n    conditionalSurveyBufferDesc.flags = RenderBufferFlag::STORAGE | RenderBufferFlag::UNORDERED_ACCESS;\n    g_conditionalSurveyBuffer = g_device->createBuffer(conditionalSurveyBufferDesc);\n\n    RenderDescriptorSetBuilder conditionalSurveyDescriptorSetBuilder;\n    conditionalSurveyDescriptorSetBuilder.begin();\n    conditionalSurveyDescriptorSetBuilder.addReadWriteStructuredBuffer(0);\n    conditionalSurveyDescriptorSetBuilder.end();\n    g_conditionalSurveyDescriptorSet = conditionalSurveyDescriptorSetBuilder.create(g_device.get());\n\n    RenderBufferStructuredView conditionalSurveyStructuredView(sizeof(uint32_t));\n    g_conditionalSurveyDescriptorSet->setBuffer(0, g_conditionalSurveyBuffer.get(), 0, &conditionalSurveyStructuredView);\n\n    pipelineLayoutBuilder.addDescriptorSet(conditionalSurveyDescriptorSetBuilder);\n\n    if (g_backend != Backend::D3D12)\n    {\n        pipelineLayoutBuilder.addPushConstant(0, 4, 24, RenderShaderStageFlag::VERTEX | RenderShaderStageFlag::PIXEL);\n    }\n    else\n    {\n        pipelineLayoutBuilder.addRootDescriptor(0, 4, RenderRootDescriptorType::CONSTANT_BUFFER);\n        pipelineLayoutBuilder.addRootDescriptor(1, 4, RenderRootDescriptorType::CONSTANT_BUFFER);\n        pipelineLayoutBuilder.addRootDescriptor(2, 4, RenderRootDescriptorType::CONSTANT_BUFFER);\n        pipelineLayoutBuilder.addPushConstant(3, 4, 4, RenderShaderStageFlag::PIXEL); // For copy/resolve shaders.\n    }\n    pipelineLayoutBuilder.end();\n    \n    g_pipelineLayout = pipelineLayoutBuilder.create(g_device.get());\n\n    g_copyShader = CREATE_SHADER(copy_vs);\n    g_copyColorShader = CREATE_SHADER(copy_color_ps);\n    auto copyDepthShader = CREATE_SHADER(copy_depth_ps);\n\n    RenderGraphicsPipelineDesc desc;\n    desc.pipelineLayout = g_pipelineLayout.get();\n    desc.vertexShader = g_copyShader.get();\n    desc.pixelShader = copyDepthShader.get();\n    desc.depthFunction = RenderComparisonFunction::ALWAYS;\n    desc.depthEnabled = true;\n    desc.depthWriteEnabled = true;\n    desc.depthTargetFormat = RenderFormat::D32_FLOAT_S8_UINT;\n    g_copyDepthPipeline = g_device->createGraphicsPipeline(desc);\n\n    g_resolveMsaaColorShaders[0] = CREATE_SHADER(resolve_msaa_color_2x);\n    g_resolveMsaaColorShaders[1] = CREATE_SHADER(resolve_msaa_color_4x);\n    g_resolveMsaaColorShaders[2] = CREATE_SHADER(resolve_msaa_color_8x);\n\n    for (size_t i = 0; i < std::size(g_resolveMsaaDepthPipelines); i++)\n    {\n        std::unique_ptr<RenderShader> pixelShader;\n        switch (i)\n        {\n        case 0:\n            pixelShader = CREATE_SHADER(resolve_msaa_depth_2x);\n            break;\n        case 1:\n            pixelShader = CREATE_SHADER(resolve_msaa_depth_4x);\n            break;\n        case 2:\n            pixelShader = CREATE_SHADER(resolve_msaa_depth_8x);\n            break;\n        }\n\n        desc = {};\n        desc.pipelineLayout = g_pipelineLayout.get();\n        desc.vertexShader = g_copyShader.get();\n        desc.pixelShader = pixelShader.get();\n        desc.depthFunction = RenderComparisonFunction::ALWAYS;\n        desc.depthEnabled = true;\n        desc.depthWriteEnabled = true;\n        desc.depthTargetFormat = RenderFormat::D32_FLOAT_S8_UINT;\n        g_resolveMsaaDepthPipelines[i] = g_device->createGraphicsPipeline(desc);\n    }\n\n    for (auto& shader : g_gaussianBlurShaders)\n        shader = std::make_unique<GuestShader>(ResourceType::PixelShader);\n\n    g_gaussianBlurShaders[GAUSSIAN_BLUR_3X3]->shader = CREATE_SHADER(gaussian_blur_3x3);\n    g_gaussianBlurShaders[GAUSSIAN_BLUR_5X5]->shader = CREATE_SHADER(gaussian_blur_5x5);\n    g_gaussianBlurShaders[GAUSSIAN_BLUR_7X7]->shader = CREATE_SHADER(gaussian_blur_7x7);\n    g_gaussianBlurShaders[GAUSSIAN_BLUR_9X9]->shader = CREATE_SHADER(gaussian_blur_9x9);\n\n    g_csdFilterShader = std::make_unique<GuestShader>(ResourceType::PixelShader);\n    g_csdFilterShader->shader = CREATE_SHADER(csd_filter_ps);\n\n    g_enhancedBurnoutBlurVSShader = std::make_unique<GuestShader>(ResourceType::VertexShader);\n    g_enhancedBurnoutBlurVSShader->shader = CREATE_SHADER(enhanced_burnout_blur_vs);\n\n    g_enhancedBurnoutBlurPSShader = std::make_unique<GuestShader>(ResourceType::PixelShader);\n    g_enhancedBurnoutBlurPSShader->shader = CREATE_SHADER(enhanced_burnout_blur_ps);\n\n    g_conditionalSurveyPSShader = std::make_unique<GuestShader>(ResourceType::PixelShader);\n    g_conditionalSurveyPSShader->shader = CREATE_SHADER(conditional_survey_ps);\n\n    CreateImGuiBackend();\n\n    auto gammaCorrectionShader = CREATE_SHADER(gamma_correction_ps);\n\n    desc = {};\n    desc.pipelineLayout = g_pipelineLayout.get();\n    desc.vertexShader = g_copyShader.get();\n    desc.pixelShader = gammaCorrectionShader.get();\n    desc.renderTargetFormat[0] = BACKBUFFER_FORMAT;\n    desc.renderTargetBlend[0] = RenderBlendDesc::Copy();\n    desc.renderTargetCount = 1;\n    g_gammaCorrectionPipeline = g_device->createGraphicsPipeline(desc);\n\n    // NOTE: We initially allocate this on host memory to make the installer work, even if the 4 GB memory allocation fails.\n    g_backBufferHolder = std::make_unique<GuestSurface>(ResourceType::RenderTarget);\n\n    g_backBuffer = g_backBufferHolder.get();\n    g_backBuffer->width = 1280;\n    g_backBuffer->height = 720;\n    g_backBuffer->format = BACKBUFFER_FORMAT;\n    g_backBuffer->textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(1, 1, 1, BACKBUFFER_FORMAT, RenderTextureFlag::RENDER_TARGET));\n\n    Video::ComputeViewportDimensions();\n    CheckSwapChain();\n    BeginCommandList();\n\n    RenderTextureBarrier blankTextureBarriers[TEXTURE_DESCRIPTOR_NULL_COUNT];\n    for (size_t i = 0; i < TEXTURE_DESCRIPTOR_NULL_COUNT; i++)\n        blankTextureBarriers[i] = RenderTextureBarrier(g_blankTextures[i].get(), RenderTextureLayout::SHADER_READ);\n\n    g_commandLists[g_frame]->barriers(RenderBarrierStage::NONE, blankTextureBarriers, std::size(blankTextureBarriers));\n\n    return true;\n}\n\nstatic uint32_t g_waitForGPUCount = 0;\n\nvoid Video::WaitForGPU()\n{\n    g_waitForGPUCount++;\n\n    // Wait for all queued frames to finish.\n    for (size_t i = 0; i < NUM_FRAMES; i++)\n    {\n        if (g_commandListStates[i])\n        {\n            g_queue->waitForCommandFence(g_commandFences[i].get());\n            g_commandListStates[i] = false;\n        }\n    }\n\n    // Execute an empty command list and wait for it to end to guarantee that any remaining presentation has finished.\n    g_commandLists[0]->begin();\n    g_commandLists[0]->end();\n    g_queue->executeCommandLists(g_commandLists[0].get(), g_commandFences[0].get());\n    g_queue->waitForCommandFence(g_commandFences[0].get());\n}\n\nstatic uint32_t getSetAddress(uint32_t base, int index) {\n    uint32_t entryOffset = index * 0xC;\n    uint32_t entryAddress = base + entryOffset;\n    uint32_t setAddress = entryAddress + sizeof(uint32_t);\n    return setAddress;\n}\n\nstatic uint32_t CreateDevice(uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, be<uint32_t>* a6)\n{\n    LOGF_WARNING(\"{:p} {:p} {:p} {:p} {:p} {:p}\\n\", reinterpret_cast<void*>(a1), reinterpret_cast<void*>(a2), reinterpret_cast<void*>(a3), reinterpret_cast<void*>(a4), reinterpret_cast<void*>(a5), reinterpret_cast<void*>(a6));\n    \n    g_xdbfTextureCache = std::unordered_map<uint16_t, GuestTexture*>();\n\n    for (auto &achievement : g_xdbfWrapper.GetAchievements(XDBF_LANGUAGE_ENGLISH))\n    {\n        if (!achievement.pImageBuffer || !achievement.ImageBufferSize)\n            continue;\n\n        g_xdbfTextureCache[achievement.ID] =\n            LoadTexture((uint8_t *)achievement.pImageBuffer, achievement.ImageBufferSize).release();\n    }\n\n    // Move backbuffer to guest memory.\n    assert(!g_memory.IsInMemoryRange(g_backBuffer) && g_backBufferHolder != nullptr);\n    g_backBuffer = g_userHeap.AllocPhysical<GuestSurface>(std::move(*g_backBufferHolder));\n\n    // Check for stale reference. BeginCommandList() gets called before CreateDevice() which is where the assignment happens.\n    if (g_renderTarget == g_backBufferHolder.get()) g_renderTarget = g_backBuffer;\n    if (g_depthStencil == g_backBufferHolder.get()) g_depthStencil = g_backBuffer;\n\n    // Free the host backbuffer.\n    g_backBufferHolder = nullptr;\n\n    auto device = g_userHeap.AllocPhysical<GuestDevice>();\n    memset(device, 0, sizeof(*device));\n\n    // Append render state functions to the end of guest function table.\n    uint32_t functionOffsetUnimplemented = PPC_CODE_BASE + PPC_CODE_SIZE;\n    g_memory.InsertFunction(functionOffsetUnimplemented, HostToGuestFunction<SetRenderStateUnimplemented>);\n    \n    uint32_t functionOffset = 0x82B79868;\n    for (size_t i = 0; i < std::size(device->setRenderStateFunctions); i++) {\n        device->setRenderStateFunctions[i] = functionOffsetUnimplemented;\n    }\n\n    // InsertFucntion doesn't work, so we have to do this manually in the end\n    for (auto& [state, function] : g_setRenderStateFunctions)\n    {\n        auto funcOffset = getSetAddress(functionOffset, state/4);\n        uint32_t addr = __builtin_bswap32(*(uint32_t*)g_memory.Translate(funcOffset));\n        printf(\"state %d of %x is %x\\n\", state, funcOffset, addr);\n        g_memory.InsertFunction(addr, function);\n        device->setRenderStateFunctions[state / 4] = addr;\n    }\n\n    for (size_t i = 0; i < std::size(device->setSamplerStateFunctions); i++)\n        device->setSamplerStateFunctions[i] = *reinterpret_cast<uint32_t*>(g_memory.Translate(0x82B79CFC + i * 0xC));\n\n    device->viewport.width = 1280.0f;\n    device->viewport.height = 720.0f;\n    device->viewport.maxZ = 1.0f;\n\n    *a6 = g_memory.MapVirtual(device);\n\n    return 0;\n}\n\nstatic void DestructResource(GuestResource* resource) \n{\n    // Needed for hack in CreateSurface (remove if fix it)\n    if (resource->type == ResourceType::RenderTarget || resource->type == ResourceType::DepthStencil)\n    {\n        const auto surface = reinterpret_cast<GuestSurface*>(resource);\n        if (surface->wasCached) {\n            return;\n        }\n    }\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::DestructResource;\n    cmd.destructResource.resource = resource;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcDestructResource(const RenderCommand& cmd)\n{\n    const auto& args = cmd.destructResource;\n    g_tempResources[g_frame].push_back(args.resource);\n}\n\nstatic uint32_t ComputeTexturePitch(GuestTexture* texture)\n{\n    return (texture->width * RenderFormatSize(texture->format) + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1);\n}\n\nstatic void LockTextureRect(GuestTexture* texture, uint32_t, GuestLockedRect* lockedRect) \n{\n    uint32_t pitch = ComputeTexturePitch(texture);\n    uint32_t slicePitch = pitch * texture->height;\n\n    if (texture->mappedMemory == nullptr)\n        texture->mappedMemory = g_userHeap.AllocPhysical(slicePitch, 0x10);\n\n    lockedRect->pitch = pitch;\n    lockedRect->bits = g_memory.MapVirtual(texture->mappedMemory);\n}\n\nstatic void UnlockTextureRect(GuestTexture* texture) \n{\n    assert(std::this_thread::get_id() == g_presentThreadId);\n\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::UnlockTextureRect;\n    cmd.unlockTextureRect.texture = texture;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcUnlockTextureRect(const RenderCommand& cmd)\n{\n    const auto& args = cmd.unlockTextureRect;\n\n    AddBarrier(args.texture, RenderTextureLayout::COPY_DEST);\n    FlushBarriers();\n\n    uint32_t pitch = ComputeTexturePitch(args.texture);\n    uint32_t slicePitch = pitch * args.texture->height;\n\n    auto allocation = g_uploadAllocators[g_frame].allocate(slicePitch, PLACEMENT_ALIGNMENT);\n    memcpy(allocation.memory, args.texture->mappedMemory, slicePitch);\n\n    g_commandLists[g_frame]->copyTextureRegion(\n        RenderTextureCopyLocation::Subresource(args.texture->texture, 0),\n        RenderTextureCopyLocation::PlacedFootprint(allocation.buffer, args.texture->format, args.texture->width, args.texture->height, 1, pitch / RenderFormatSize(args.texture->format), allocation.offset));\n}\n\nstatic void* LockBuffer(GuestBuffer* buffer, uint32_t flags)\n{\n    buffer->lockedReadOnly = (flags & 0x10) != 0;\n\n    if (buffer->mappedMemory == nullptr)\n        buffer->mappedMemory = g_userHeap.AllocPhysical(buffer->dataSize, 0x10);\n\n    return buffer->mappedMemory;\n}\n\nstatic void* LockVertexBuffer(GuestBuffer* buffer, uint32_t, uint32_t, uint32_t flags)\n{\n    return LockBuffer(buffer, flags);\n}\n\nstatic std::atomic<uint32_t> g_bufferUploadCount = 0;\n\ntemplate<typename T>\nstatic void UnlockBuffer(GuestBuffer* buffer, bool useCopyQueue)\n{\n    auto copyBuffer = [&](T* dest)\n        {\n            auto src = reinterpret_cast<const T*>(buffer->mappedMemory);\n\n            for (size_t i = 0; i < buffer->dataSize; i += sizeof(T))\n            {\n                *dest = ByteSwap(*src);\n                ++dest;\n                ++src;\n            }\n        };\n\n    if (useCopyQueue && g_capabilities.gpuUploadHeap)\n    {\n        copyBuffer(reinterpret_cast<T*>(buffer->buffer->map()));\n        buffer->buffer->unmap();\n    }\n    else\n    {\n        auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(buffer->dataSize));\n        copyBuffer(reinterpret_cast<T*>(uploadBuffer->map()));\n        uploadBuffer->unmap();\n\n        if (useCopyQueue)\n        {\n            ExecuteCopyCommandList([&]\n                {\n                    g_copyCommandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize);\n                });\n        }\n        else\n        {\n            auto& commandList = g_commandLists[g_frame];\n\n            commandList->barriers(RenderBarrierStage::COPY, RenderBufferBarrier(buffer->buffer.get(), RenderBufferAccess::WRITE));\n            commandList->copyBufferRegion(buffer->buffer->at(0), uploadBuffer->at(0), buffer->dataSize);\n            commandList->barriers(RenderBarrierStage::GRAPHICS, RenderBufferBarrier(buffer->buffer.get(), RenderBufferAccess::READ));\n\n            g_tempBuffers[g_frame].emplace_back(std::move(uploadBuffer));\n        }\n    }\n\n    g_bufferUploadCount++;\n}\n\ntemplate<typename T>\nstatic void UnlockBuffer(GuestBuffer* buffer)\n{\n    if (!buffer->lockedReadOnly)\n    {\n        UnlockBuffer<T>(buffer, true);\n    }\n}\n\nstatic void ProcUnlockBuffer16(const RenderCommand& cmd)\n{\n    UnlockBuffer<uint16_t>(cmd.unlockBuffer.buffer, false);\n}\n\nstatic void ProcUnlockBuffer32(const RenderCommand& cmd)\n{\n    UnlockBuffer<uint32_t>(cmd.unlockBuffer.buffer, false);\n}\n\nstatic void UnlockVertexBuffer(GuestBuffer* buffer)\n{\n    UnlockBuffer<uint32_t>(buffer);\n}\n\nstatic void GetVertexBufferDesc(GuestBuffer* buffer, GuestBufferDesc* desc) \n{\n    desc->size = buffer->dataSize;\n}\n\nstatic void* LockIndexBuffer(GuestBuffer* buffer, uint32_t, uint32_t, uint32_t flags) \n{\n    return LockBuffer(buffer, flags);\n}\n\nstatic void UnlockIndexBuffer(GuestBuffer* buffer) \n{\n    if (buffer->guestFormat == D3DFMT_INDEX32)\n        UnlockBuffer<uint32_t>(buffer);\n    else\n        UnlockBuffer<uint16_t>(buffer);\n}\n\nstatic void GetIndexBufferDesc(GuestBuffer* buffer, GuestBufferDesc* desc)\n{\n    desc->format = buffer->guestFormat;\n    desc->size = buffer->dataSize;\n}\n\nstatic void GetSurfaceDesc(GuestSurface* surface, GuestSurfaceDesc* desc) \n{\n    if (surface->width == 0 && surface->height == 0) {\n        LOGF_WARNING(\"{:p} {:d} {:d} \\n\", reinterpret_cast<void*>(desc), surface->width, surface->height);\n        __builtin_trap();\n    }\n    desc->width = surface->width;\n    desc->height = surface->height;\n    desc->format = surface->guestFormat;\n    desc->type = 4; // D3DRTYPE_SURFACE\n    // desc->multiSampleType = 0;\n    if (surface->sampleCount == RenderSampleCount::COUNT_1) {\n        desc->multiSampleType = 0;\n    } else if (surface->sampleCount == RenderSampleCount::COUNT_2) {\n        desc->multiSampleType = 1;\n    } else {\n        desc->multiSampleType = 2;\n    }\n    desc->multiSampleQuality = 0;\n    desc->usage = 0;\n}\n\nstatic void GetVertexDeclaration(GuestVertexDeclaration* vertexDeclaration, GuestVertexElement* vertexElements, be<uint32_t>* count) \n{\n    memcpy(vertexElements, vertexDeclaration->vertexElements.get(), vertexDeclaration->vertexElementCount * sizeof(GuestVertexElement));\n    *count = vertexDeclaration->vertexElementCount;\n}\n\nstatic uint32_t HashVertexDeclaration(uint32_t vertexDeclaration) \n{\n    // Vertex declarations are cached on host side, so the pointer itself can be used.\n    return vertexDeclaration;\n}\n\nstatic const char *DeviceTypeName(RenderDeviceType type)\n{\n    switch (type) \n    {\n    case RenderDeviceType::INTEGRATED:\n        return \"Integrated\";\n    case RenderDeviceType::DISCRETE:\n        return \"Discrete\";\n    case RenderDeviceType::VIRTUAL:\n        return \"Virtual\";\n    case RenderDeviceType::CPU:\n        return \"CPU\";\n    default:\n        return \"Unknown\";\n    }\n}\n\nstatic void DrawProfiler()\n{\n    bool toggleProfiler = SDL_GetKeyboardState(nullptr)[SDL_SCANCODE_F1] != 0;\n\n    if (!g_profilerWasToggled && toggleProfiler)\n    {\n        g_profilerVisible = !g_profilerVisible;\n\n        GameWindow::SetFullscreenCursorVisibility(App::s_isInit ? g_profilerVisible : true);\n    }\n\n    g_profilerWasToggled = toggleProfiler;\n\n    if (!g_profilerVisible)\n        return;\n\n    ImFont* font = ImFontAtlasSnapshot::GetFont(\"FOT-RodinPro-DB.otf\");\n    float defaultScale = font->Scale;\n    font->Scale = ImGui::GetDefaultFont()->FontSize / font->FontSize;\n    ImGui::PushFont(font);\n\n#define IMGUI_GENERIC_ROW(name, value, ...) \\\n    ImGui::TableNextColumn(); \\\n    ImGui::Text(name); \\\n    ImGui::TableNextColumn(); \\\n    ImGui::Text(value, __VA_ARGS__);\n\n    if (ImGui::Begin(\"Profiler\", &g_profilerVisible))\n    {\n        g_applicationValues[g_profilerValueIndex] = App::s_deltaTime * 1000.0;\n\n        const double applicationAvg = std::accumulate(g_applicationValues, g_applicationValues + PROFILER_VALUE_COUNT, 0.0) / PROFILER_VALUE_COUNT;\n        double gpuFrameAvg = g_gpuFrameProfiler.UpdateAndReturnAverage();\n        double presentAvg = g_presentProfiler.UpdateAndReturnAverage();\n        double frameFenceAvg = g_frameFenceProfiler.UpdateAndReturnAverage();\n        double presentWaitAvg = g_presentWaitProfiler.UpdateAndReturnAverage();\n        double swapChainAcquireAvg = g_swapChainAcquireProfiler.UpdateAndReturnAverage();\n\n        if (ImGui::CollapsingHeader(\"Performance\", ImGuiTreeNodeFlags_DefaultOpen))\n        {\n            if (ImPlot::BeginPlot(\"Frame Time\"))\n            {\n                ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 20.0);\n                ImPlot::SetupAxis(ImAxis_Y1, \"ms\", ImPlotAxisFlags_None);\n                ImPlot::PlotLine<double>(\"Application\", g_applicationValues, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex);\n                ImPlot::PlotLine<double>(\"GPU Frame\", g_gpuFrameProfiler.values, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex);\n                ImPlot::PlotLine<double>(\"Present\", g_presentProfiler.values, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex);\n                ImPlot::PlotLine<double>(\"Present Wait\", g_presentWaitProfiler.values, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex);\n                ImPlot::PlotLine<double>(\"Frame Fence\", g_frameFenceProfiler.values, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex);\n                ImPlot::PlotLine<double>(\"Swap Chain Acquire\", g_swapChainAcquireProfiler.values, PROFILER_VALUE_COUNT, 1.0, 0.0, ImPlotLineFlags_None, g_profilerValueIndex);\n                ImPlot::EndPlot();\n            }\n\n            g_profilerValueIndex = (g_profilerValueIndex + 1) % PROFILER_VALUE_COUNT;\n\n            if (ImGui::BeginTable(\"Performance\", 5))\n            {\n                ImGui::TableSetupColumn(\"Name\");\n                ImGui::TableSetupColumn(\"Current Time\");\n                ImGui::TableSetupColumn(\"Average Time\");\n                ImGui::TableSetupColumn(\"Current FPS\");\n                ImGui::TableSetupColumn(\"Average FPS\");\n                ImGui::TableHeadersRow();\n\n                auto drawPerfRow = [](const char* name, double ms, double msAvg, bool showFPS = false, double fps = 0, double fpsAvg = 0)\n                {\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"%s\", name);\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"%g ms\", ms);\n                    ImGui::TableNextColumn();\n                    ImGui::Text(\"%g ms\", msAvg);\n                    ImGui::TableNextColumn();\n\n                    if (showFPS)\n                        ImGui::Text(\"%g FPS\", fps);\n\n                    ImGui::TableNextColumn();\n\n                    if (showFPS)\n                        ImGui::Text(\"%g FPS\", fpsAvg);\n                };\n\n                // -------- Name ---------------- Current Time --------------------------- Average Time -------- Current FPS ----------------------------- Average FPS ---------- //\n                drawPerfRow(\"Application\",        App::s_deltaTime * 1000.0,               applicationAvg, true, 1.0 / App::s_deltaTime,                   1000.0 / applicationAvg);\n                drawPerfRow(\"GPU Frame\",          g_gpuFrameProfiler.value.load(),         gpuFrameAvg,    true, 1000.0 / g_gpuFrameProfiler.value.load(), 1000.0 / gpuFrameAvg   );\n                drawPerfRow(\"Present\",            g_presentProfiler.value.load(),          presentAvg,     true, 1000.0 / g_presentProfiler.value.load(),  1000.0 / presentAvg    );\n                drawPerfRow(\"Present Wait\",       g_presentWaitProfiler.value.load(),      presentWaitAvg                                                                         );\n                drawPerfRow(\"Frame Fence\",        g_frameFenceProfiler.value.load(),       frameFenceAvg                                                                          );\n                drawPerfRow(\"Swap Chain Acquire\", g_swapChainAcquireProfiler.value.load(), swapChainAcquireAvg                                                                    );\n\n                ImGui::EndTable();\n            }\n\n            ImGui::Separator();\n\n            ImGui::Checkbox(\"Show FPS\", &Config::ShowFPS.Value);\n        }\n\n        if (g_userHeap.heap != nullptr && g_userHeap.physicalHeap != nullptr)\n        {\n            if (ImGui::CollapsingHeader(\"Memory\", ImGuiTreeNodeFlags_DefaultOpen))\n            {\n                O1HeapDiagnostics diagnostics, physicalDiagnostics;\n                {\n                    std::lock_guard lock(g_userHeap.mutex);\n                    diagnostics = o1heapGetDiagnostics(g_userHeap.heap);\n                }\n                {\n                    std::lock_guard lock(g_userHeap.physicalMutex);\n                    physicalDiagnostics = o1heapGetDiagnostics(g_userHeap.physicalHeap);\n                }\n\n                if (ImGui::BeginTable(\"Memory\", 2))\n                {\n                    IMGUI_GENERIC_ROW(\"Heap Allocated\", \"%d MB\", int32_t(diagnostics.allocated / (1024 * 1024)));\n                    IMGUI_GENERIC_ROW(\"Physical Heap Allocated\", \"%d MB\", int32_t(diagnostics.allocated / (1024 * 1024)));\n\n                    ImGui::EndTable();\n                }\n            }\n        }\n\n        if (ImGui::CollapsingHeader(\"GPU\", ImGuiTreeNodeFlags_DefaultOpen))\n        {\n            std::string backend;\n\n            switch (g_backend)\n            {\n                case Backend::VULKAN:\n                    backend = \"Vulkan\";\n                    break;\n\n                case Backend::D3D12:\n                    backend = \"D3D12\";\n                    break;\n\n                case Backend::METAL:\n                    backend = \"Metal\";\n                    break;\n            }\n\n            if (ImGui::BeginTable(\"GPU\", 2))\n            {\n                IMGUI_GENERIC_ROW(\"API\", \"%s\", backend.c_str());\n\n                if (auto pSDLVideoDriver = SDL_GetCurrentVideoDriver())\n                {\n                    IMGUI_GENERIC_ROW(\"SDL Video Driver\", \"%s\", pSDLVideoDriver);\n                }\n\n                IMGUI_GENERIC_ROW(\"Device\", \"%s\", g_device->getDescription().name.c_str());\n                IMGUI_GENERIC_ROW(\"Device Type\", \"%s\", DeviceTypeName(g_device->getDescription().type));\n                IMGUI_GENERIC_ROW(\"VRAM\", \"%.2f MiB\", (double)(g_device->getDescription().dedicatedVideoMemory) / (1024.0 * 1024.0));\n                IMGUI_GENERIC_ROW(\"GPU Waits\", \"%d\", int32_t(g_waitForGPUCount));\n                IMGUI_GENERIC_ROW(\"Buffer Uploads\", \"%d\", int32_t(g_bufferUploadCount));\n\n                IMGUI_GENERIC_ROW(\"Resolution\", \"%dx%d (%dx%d)\",\n                    Video::s_viewportWidth, Video::s_viewportHeight,\n                    uint32_t(round(Video::s_viewportWidth * Config::ResolutionScale)),\n                    uint32_t(round(Video::s_viewportHeight * Config::ResolutionScale)));\n\n                ImGui::EndTable();\n            }\n\n            ImGui::Separator();\n\n            if (ImGui::TreeNode(\"Devices\"))\n            {\n                ImGui::Indent();\n\n                if (ImGui::BeginTable(\"Devices\", 2))\n                {\n                    auto deviceIndex = 0;\n\n                    for (const auto& deviceName : g_interface->getDeviceNames())\n                    {\n                        ImGui::TableNextColumn();\n                        ImGui::Text(\"Device #%d\", deviceIndex++);\n                        ImGui::TableNextColumn();\n                        ImGui::Text(\"%s\", deviceName.c_str());\n                        ImGui::SameLine();\n                    }\n\n                    ImGui::EndTable();\n                }\n\n                ImGui::Unindent();\n                ImGui::TreePop();\n            }\n\n            if (ImGui::TreeNode(\"Features\"))\n            {\n                ImGui::Indent();\n\n                if (ImGui::BeginTable(\"Features\", 2))\n                {\n                    IMGUI_GENERIC_ROW(\"Dynamic Depth Bias\", \"%s\", g_capabilities.dynamicDepthBias ? \"Supported\" : \"Unsupported\");\n                    IMGUI_GENERIC_ROW(\"GPU Upload Heap\", \"%s\", g_capabilities.gpuUploadHeap ? \"Supported\" : \"Unsupported\");\n                    IMGUI_GENERIC_ROW(\"Hardware Resolve Modes\", \"%s\", g_capabilities.resolveModes ? \"Supported\" : \"Unsupported\");\n                    IMGUI_GENERIC_ROW(\"Present Wait\", \"%s\", g_capabilities.presentWait ? \"Supported\" : \"Unsupported\");\n                    IMGUI_GENERIC_ROW(\"Triangle Fan\", \"%s\", g_capabilities.triangleFan ? \"Supported\" : \"Unsupported\");\n                    IMGUI_GENERIC_ROW(\"Triangle Strip Workaround\", \"%s\", g_triangleStripWorkaround ? \"Enabled\" : \"Disabled\");\n                    IMGUI_GENERIC_ROW(\"UMA\", \"%s\", g_capabilities.uma ? \"Supported\" : \"Unsupported\");\n\n                    ImGui::EndTable();\n                }\n\n                ImGui::Unindent();\n                ImGui::TreePop();\n            }\n        }\n    }\n\n#undef IMGUI_GENERIC_ROW\n\n    ImGui::End();\n    ImGui::PopFont();\n\n    font->Scale = defaultScale;\n}\n\nstatic void DrawFPS()\n{\n    if (!Config::ShowFPS)\n        return;\n\n    double time = ImGui::GetTime();\n    static double updateTime = time;\n    static double fps = 0;\n    static double totalDeltaTime = 0.0;\n    static uint32_t totalDeltaCount = 0;\n\n    totalDeltaTime += g_presentProfiler.value.load();\n    totalDeltaCount++;\n\n    if (time - updateTime >= 1.0f)\n    {\n        fps = 1000.0 / std::max(totalDeltaTime / double(totalDeltaCount), 1.0);\n        updateTime = time;\n        totalDeltaTime = 0.0;\n        totalDeltaCount = 0;\n    }\n\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto fmt = fmt::format(\"FPS: {:.2f}\", fps);\n    auto font = ImFontAtlasSnapshot::GetFont(\"FOT-RodinPro-DB.otf\");\n    auto fontSize = Scale(10);\n    auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, fmt.c_str());\n\n    ImVec2 min = { Scale(40), Scale(30) };\n    ImVec2 max = { min.x + std::max(Scale(75), textSize.x + Scale(10)), min.y + Scale(15) };\n    ImVec2 textPos = { min.x + Scale(2), CENTRE_TEXT_VERT(min, max, textSize) + Scale(0.2f) };\n\n    drawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 200));\n    drawList->AddText(font, fontSize, textPos, IM_COL32_WHITE, fmt.c_str());\n}\n\nstatic void DrawImGui()\n{\n    ImGui_ImplSDL2_NewFrame();\n\n    auto& io = ImGui::GetIO();\n    io.DisplaySize = { float(Video::s_viewportWidth), float(Video::s_viewportHeight) };\n\n    // ImGui doesn't know that we center the screen for specific aspect ratio\n    // settings, which causes mouse events to not work correctly. To fix this, \n    // we can adjust the mouse events before ImGui processes them.\n    uint32_t width = g_swapChain->getWidth();\n    uint32_t height = g_swapChain->getHeight();\n    float mousePosScaleX = float(width) / float(GameWindow::s_width);\n    float mousePosScaleY = float(height) / float(GameWindow::s_height);\n    float mousePosOffsetX = (width - Video::s_viewportWidth) / 2.0f;\n    float mousePosOffsetY = (height - Video::s_viewportHeight) / 2.0f;\n    for (int i = 0; i < io.Ctx->InputEventsQueue.Size; i++)\n    {\n        auto& e = io.Ctx->InputEventsQueue[i];\n        if (e.Type == ImGuiInputEventType_MousePos)\n        {\n            if (e.MousePos.PosX != -FLT_MAX)\n            {\n                e.MousePos.PosX *= mousePosScaleX;\n                e.MousePos.PosX -= mousePosOffsetX;\n            }\n\n            if (e.MousePos.PosY != -FLT_MAX)\n            {\n                e.MousePos.PosY *= mousePosScaleY;\n                e.MousePos.PosY -= mousePosOffsetY;\n            }\n        }\n    }\n\n    ImGui::NewFrame();\n\n    ResetImGuiCallbacks();\n\n#ifdef ASYNC_PSO_DEBUG\n    if (ImGui::Begin(\"Async PSO Stats\"))\n    {\n        ImGui::Text(\"Pipelines Created In Render Thread: %d\", g_pipelinesCreatedInRenderThread.load());\n        ImGui::Text(\"Pipelines Created Asynchronously: %d\", g_pipelinesCreatedAsynchronously.load());\n        ImGui::Text(\"Pipelines Dropped: %d\", g_pipelinesDropped.load());\n        ImGui::Text(\"Pipelines Currently Compiling: %d\", g_pipelinesCurrentlyCompiling.load());\n        ImGui::Text(\"Compiling Pipeline Task Count: %d\", g_compilingPipelineTaskCount.load());\n        ImGui::Text(\"Pending Pipeline Task Count: %d\", g_pendingPipelineTaskCount.load());\n\n        std::lock_guard lock(g_debugMutex);\n        ImGui::TextUnformatted(g_pipelineDebugText.c_str());\n    }\n    ImGui::End();\n#endif\n\n    UpdateImGuiUtils();\n    AchievementMenu::Draw();\n    OptionsMenu::Draw();\n    InstallerWizard::Draw();\n    ButtonWindow::Draw();\n    MessageWindow::Draw();\n    AchievementOverlay::Draw();\n    Fader::Draw();\n    BlackBar::Draw();\n\n    assert(ImGui::GetBackgroundDrawList()->_ClipRectStack.Size == 1 && \"Some clip rects were not removed from the stack!\");\n\n    DrawFPS();\n    DrawProfiler();\n    ImGui::Render();\n\n    auto drawData = ImGui::GetDrawData();\n    if (drawData->CmdListsCount != 0)\n    {\n        RenderCommand cmd;\n        cmd.type = RenderCommandType::DrawImGui;\n        g_renderQueue.enqueue(cmd);\n    }\n}\n\nstatic void SetFramebuffer(GuestSurface *renderTarget, GuestSurface *depthStencil, bool settingForClear);\n\nstatic void ProcDrawImGui(const RenderCommand& cmd)\n{\n    // Make sure the backbuffer is the current target.\n    AddBarrier(g_backBuffer, RenderTextureLayout::COLOR_WRITE);\n    FlushBarriers();\n    SetFramebuffer(g_backBuffer, nullptr, false);\n\n    auto& commandList = g_commandLists[g_frame];\n    auto pipeline = g_imPipeline.get();\n\n    commandList->setGraphicsPipelineLayout(g_imPipelineLayout.get());\n    commandList->setPipeline(pipeline);\n    commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 0);\n    commandList->setGraphicsDescriptorSet(g_samplerDescriptorSet.get(), 1);\n\n    auto& drawData = *ImGui::GetDrawData();\n    commandList->setViewports(RenderViewport(drawData.DisplayPos.x, drawData.DisplayPos.y, drawData.DisplaySize.x, drawData.DisplaySize.y));\n\n    ImGuiPushConstants pushConstants{};\n    pushConstants.displaySize = drawData.DisplaySize;\n    pushConstants.inverseDisplaySize = { 1.0f / drawData.DisplaySize.x, 1.0f / drawData.DisplaySize.y };\n    commandList->setGraphicsPushConstants(0, &pushConstants);\n\n    size_t pushConstantRangeMin = ~0;\n    size_t pushConstantRangeMax = 0;\n\n    auto setPushConstants = [&](void* destination, const void* source, size_t size)\n        {\n            bool dirty = memcmp(destination, source, size) != 0;\n\n            memcpy(destination, source, size);\n\n            if (dirty)\n            {\n                size_t offset = reinterpret_cast<size_t>(destination) - reinterpret_cast<size_t>(&pushConstants);\n                pushConstantRangeMin = std::min(pushConstantRangeMin, offset);\n                pushConstantRangeMax = std::max(pushConstantRangeMax, offset + size);\n            }\n        };\n\n    ImRect clipRect{};\n\n    for (int i = 0; i < drawData.CmdListsCount; i++)\n    {\n        auto& drawList = drawData.CmdLists[i];\n\n        auto vertexBufferAllocation = g_uploadAllocators[g_frame].allocate<false>(drawList->VtxBuffer.Data, drawList->VtxBuffer.Size * sizeof(ImDrawVert), alignof(ImDrawVert));\n        auto indexBufferAllocation = g_uploadAllocators[g_frame].allocate<false>(drawList->IdxBuffer.Data, drawList->IdxBuffer.Size * sizeof(uint16_t), alignof(uint16_t));\n\n        const RenderVertexBufferView vertexBufferView(vertexBufferAllocation.buffer->at(vertexBufferAllocation.offset), drawList->VtxBuffer.Size * sizeof(ImDrawVert));\n        const RenderInputSlot inputSlot(0, sizeof(ImDrawVert));\n        commandList->setVertexBuffers(0, &vertexBufferView, 1, &inputSlot);\n\n        const RenderIndexBufferView indexBufferView(indexBufferAllocation.buffer->at(indexBufferAllocation.offset), drawList->IdxBuffer.Size * sizeof(uint16_t), RenderFormat::R16_UINT);\n        commandList->setIndexBuffer(&indexBufferView);\n\n        for (int j = 0; j < drawList->CmdBuffer.Size; j++)\n        {\n            auto& drawCmd = drawList->CmdBuffer[j];\n            if (drawCmd.UserCallback != nullptr)\n            {\n                auto callbackData = reinterpret_cast<const ImGuiCallbackData*>(drawCmd.UserCallbackData);\n\n                switch (static_cast<ImGuiCallback>(reinterpret_cast<size_t>(drawCmd.UserCallback)))\n                {\n                case ImGuiCallback::SetGradient:\n                    setPushConstants(&pushConstants.boundsMin, &callbackData->setGradient, sizeof(callbackData->setGradient));\n                    break;       \n                case ImGuiCallback::SetShaderModifier:\n                    setPushConstants(&pushConstants.shaderModifier, &callbackData->setShaderModifier, sizeof(callbackData->setShaderModifier));\n                    break;\n                case ImGuiCallback::SetOrigin:\n                    setPushConstants(&pushConstants.origin, &callbackData->setOrigin, sizeof(callbackData->setOrigin));\n                    break;\n                case ImGuiCallback::SetScale:\n                    setPushConstants(&pushConstants.scale, &callbackData->setScale, sizeof(callbackData->setScale));\n                    break;       \n                case ImGuiCallback::SetMarqueeFade:\n                    setPushConstants(&pushConstants.boundsMin, &callbackData->setMarqueeFade, sizeof(callbackData->setMarqueeFade));\n                    break;\n                case ImGuiCallback::SetOutline:\n                    setPushConstants(&pushConstants.outline, &callbackData->setOutline, sizeof(callbackData->setOutline));\n                    break;\n                case ImGuiCallback::SetProceduralOrigin:\n                    setPushConstants(&pushConstants.proceduralOrigin, &callbackData->setProceduralOrigin, sizeof(callbackData->setProceduralOrigin));\n                    break;\n                case ImGuiCallback::SetAdditive:\n                {\n                    auto pipelineToSet = callbackData->setAdditive.enabled ? g_imAdditivePipeline.get() : g_imPipeline.get();\n                    if (pipeline != pipelineToSet)\n                    {\n                        commandList->setPipeline(pipelineToSet);\n                        pipeline = pipelineToSet;\n                    }\n                    break;\n                }\n                default:\n                    assert(false && \"Unknown ImGui callback type.\");\n                    break;\n                }\n            }\n            else\n            {\n                if (drawCmd.ClipRect.z <= drawCmd.ClipRect.x || drawCmd.ClipRect.w <= drawCmd.ClipRect.y)\n                    continue;\n\n                auto texture = reinterpret_cast<GuestTexture*>(drawCmd.TextureId);\n                uint32_t descriptorIndex = TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D;\n                if (texture != nullptr)\n                {\n                    if (texture->layout != RenderTextureLayout::SHADER_READ)\n                    {\n                        commandList->barriers(RenderBarrierStage::GRAPHICS | RenderBarrierStage::COPY,\n                            RenderTextureBarrier(texture->texture, RenderTextureLayout::SHADER_READ));\n\n                        texture->layout = RenderTextureLayout::SHADER_READ;\n                    }\n\n                    descriptorIndex = texture->descriptorIndex;\n\n                    if (texture == g_imFontTexture.get())\n                        descriptorIndex |= 0x80000000;\n\n                    setPushConstants(&pushConstants.texture2DDescriptorIndex, &descriptorIndex, sizeof(descriptorIndex));\n                }\n\n                if (pushConstantRangeMin < pushConstantRangeMax)\n                {\n                    commandList->setGraphicsPushConstants(0, reinterpret_cast<const uint8_t*>(&pushConstants) + pushConstantRangeMin, pushConstantRangeMin, pushConstantRangeMax - pushConstantRangeMin);\n                    pushConstantRangeMin = ~0;\n                    pushConstantRangeMax = 0;\n                }\n\n                if (memcmp(&clipRect, &drawCmd.ClipRect, sizeof(clipRect)) != 0)\n                {\n                    commandList->setScissors(RenderRect(int32_t(drawCmd.ClipRect.x), int32_t(drawCmd.ClipRect.y), int32_t(drawCmd.ClipRect.z), int32_t(drawCmd.ClipRect.w)));\n                    clipRect = drawCmd.ClipRect;\n                }\n\n                commandList->drawIndexedInstanced(drawCmd.ElemCount, 1, drawCmd.IdxOffset, drawCmd.VtxOffset, 0);\n            }\n        }\n    }\n}\n\n// We have to check for this to properly handle the following situation:\n// 1. Wait on swap chain.\n// 2. Create loading thread.\n// 3. Loading thread also waits on swap chain.\n// 4. Loading thread presents and quits.\n// 5. After the loading thread quits, application also presents.\nstatic bool g_pendingWaitOnSwapChain = true;\n\nvoid Video::WaitOnSwapChain()\n{\n    if (g_pendingWaitOnSwapChain)\n    {\n        if (g_swapChainValid)\n        {\n            g_presentWaitProfiler.Begin();\n            g_swapChain->wait();\n            g_presentWaitProfiler.End();\n        }\n\n        g_pendingWaitOnSwapChain = false;\n    }\n}\n\nstatic bool g_shouldPrecompilePipelines;\nstatic std::atomic<bool> g_executedCommandList;\n\nvoid Video::Present() \n{\n    g_readyForCommands = false;\n\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::ExecutePendingStretchRectCommands;\n    g_renderQueue.enqueue(cmd);\n\n    DrawImGui();\n\n    cmd.type = RenderCommandType::ExecuteCommandList;\n    g_renderQueue.enqueue(cmd);\n\n    // All the shaders are available at this point. We can precompile embedded PSOs then.\n    if (g_shouldPrecompilePipelines)\n    {\n//        EnqueuePipelineTask(PipelineTaskType::PrecompilePipelines, {});\n        g_shouldPrecompilePipelines = false;\n    }\n\n    g_executedCommandList.wait(false);\n    g_executedCommandList = false;\n\n    if (g_swapChainValid)\n    {\n        if (g_pendingWaitOnSwapChain)\n        {\n            g_presentWaitProfiler.Begin();\n            g_swapChain->wait(); // Never gonna happen outside loading threads as explained above.\n            g_presentWaitProfiler.End();\n        }\n\n        RenderCommandSemaphore* signalSemaphores[] = { g_renderSemaphores[g_frame].get() };\n        g_swapChainValid = g_swapChain->present(g_backBufferIndex, signalSemaphores, std::size(signalSemaphores));\n    }\n\n    g_pendingWaitOnSwapChain = true;\n\n    g_frame = g_nextFrame;\n    g_nextFrame = (g_frame + 1) % NUM_FRAMES;\n\n    if (g_commandListStates[g_frame])\n    {\n        g_frameFenceProfiler.Begin();\n        g_queue->waitForCommandFence(g_commandFences[g_frame].get());\n        g_frameFenceProfiler.End();\n        g_commandListStates[g_frame] = false;\n\n        // Update the GPU profiler with the results from the timestamps of the frame.\n        g_queryPools[g_frame]->queryResults();\n        const uint64_t *frameTimestamps = g_queryPools[g_frame]->getResults();\n        g_gpuFrameProfiler.Set(double(frameTimestamps[1] - frameTimestamps[0]) / 1000000.0);\n    }\n\n    g_dirtyStates = DirtyStates(true);\n    g_uploadAllocators[g_frame].reset();\n    g_intermediaryUploadAllocator.reset();\n    g_triangleFanIndexData.reset();\n    g_quadIndexData.reset();\n\n    CheckSwapChain();\n\n    cmd.type = RenderCommandType::BeginCommandList;\n    g_renderQueue.enqueue(cmd);\n\n    if (Config::FPS >= FPS_MIN && Config::FPS < FPS_MAX)\n    {\n        using namespace std::chrono_literals;\n\n        static std::chrono::steady_clock::time_point s_next;\n\n        auto now = std::chrono::steady_clock::now();\n\n        if (now < s_next)\n        {\n            std::this_thread::sleep_for(std::chrono::floor<std::chrono::milliseconds>(s_next - now - 2ms));\n\n            while ((now = std::chrono::steady_clock::now()) < s_next)\n                std::this_thread::yield();\n        }\n        else\n        {\n            s_next = now;\n        }\n\n        s_next += 1000000000ns / Config::FPS;\n    }\n\n    g_presentProfiler.Reset();\n}\n\nvoid Video::StartPipelinePrecompilation()\n{\n    g_shouldPrecompilePipelines = true;\n}\n\nstatic void SetRootDescriptor(const UploadAllocation& allocation, size_t index)\n{\n    auto& commandList = g_commandLists[g_frame];\n\n    if (g_backend != Backend::D3D12)\n        commandList->setGraphicsPushConstants(0, &allocation.deviceAddress, 8 * index, 8);\n    else\n        commandList->setGraphicsRootDescriptor(allocation.buffer->at(allocation.offset), index);\n}\n\nstatic void ProcExecuteCommandList(const RenderCommand& cmd)\n{    \n    if (g_swapChainValid)\n    {\n        auto swapChainTexture = g_swapChain->getTexture(g_backBufferIndex);\n        if (g_backBuffer->texture == g_intermediaryBackBufferTexture.get())\n        {\n            struct\n            {\n                float gamma;\n                uint32_t textureDescriptorIndex;\n\n                int32_t viewportOffsetX;\n                int32_t viewportOffsetY;\n                int32_t viewportWidth;\n                int32_t viewportHeight;\n            } constants;\n\n            constants.gamma = 0.85f;\n\n            float offset = (Config::Brightness - 0.5f) * 1.2f;\n\n            constants.gamma = 1.0f / std::clamp(constants.gamma + offset, 0.1f, 4.0f);\n            constants.textureDescriptorIndex = g_intermediaryBackBufferTextureDescriptorIndex;\n\n            constants.viewportOffsetX = (int32_t(g_swapChain->getWidth()) - int32_t(Video::s_viewportWidth)) / 2;\n            constants.viewportOffsetY = (int32_t(g_swapChain->getHeight()) - int32_t(Video::s_viewportHeight)) / 2;\n            constants.viewportWidth = Video::s_viewportWidth;\n            constants.viewportHeight = Video::s_viewportHeight;\n\n            auto &framebuffer = g_backBuffer->framebuffers[swapChainTexture];\n            if (!framebuffer)\n            {\n                RenderFramebufferDesc desc;\n                desc.colorAttachments = const_cast<const RenderTexture **>(&swapChainTexture);\n                desc.colorAttachmentsCount = 1;\n                framebuffer = g_device->createFramebuffer(desc);\n            }\n\n            RenderTextureBarrier srcBarriers[] =\n            {\n                RenderTextureBarrier(g_intermediaryBackBufferTexture.get(), RenderTextureLayout::SHADER_READ),\n                RenderTextureBarrier(swapChainTexture, RenderTextureLayout::COLOR_WRITE)\n            };\n\n            auto &commandList = g_commandLists[g_frame];\n            commandList->barriers(RenderBarrierStage::GRAPHICS, srcBarriers, std::size(srcBarriers));\n            commandList->setGraphicsPipelineLayout(g_pipelineLayout.get());\n            commandList->setPipeline(g_gammaCorrectionPipeline.get());\n            commandList->setGraphicsDescriptorSet(g_textureDescriptorSet.get(), 0);\n            SetRootDescriptor(g_uploadAllocators[g_frame].allocate<false>(&constants, sizeof(constants), 0x100), 2);\n            commandList->setFramebuffer(framebuffer.get());\n            commandList->setViewports(RenderViewport(0.0f, 0.0f, g_swapChain->getWidth(), g_swapChain->getHeight()));\n            commandList->setScissors(RenderRect(0, 0, g_swapChain->getWidth(), g_swapChain->getHeight()));\n            commandList->drawInstanced(6, 1, 0, 0);\n            commandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(swapChainTexture, RenderTextureLayout::PRESENT));\n        }\n        else\n        {\n            AddBarrier(g_backBuffer, RenderTextureLayout::PRESENT);\n            FlushBarriers();\n        }\n    }\n\n    auto &commandList = g_commandLists[g_frame];\n    commandList->writeTimestamp(g_queryPools[g_frame].get(), 1);\n    commandList->end();\n\n    if (g_swapChainValid)\n    {\n        const RenderCommandList *commandLists[] = { commandList.get() };\n        RenderCommandSemaphore *waitSemaphores[] = { g_acquireSemaphores[g_frame].get() };\n        RenderCommandSemaphore *signalSemaphores[] = { g_renderSemaphores[g_frame].get() };\n\n        g_queue->executeCommandLists(\n            commandLists, std::size(commandLists),\n            waitSemaphores, std::size(waitSemaphores),\n            signalSemaphores, std::size(signalSemaphores),\n            g_commandFences[g_frame].get());\n    }\n    else\n    {\n        g_queue->executeCommandLists(commandList.get(), g_commandFences[g_frame].get());\n    }\n\n    g_commandListStates[g_frame] = true;\n\n    g_executedCommandList = true;\n    g_executedCommandList.notify_one();\n}\n\nstatic void ProcBeginCommandList(const RenderCommand& cmd)\n{\n    DestructTempResources();\n    BeginCommandList();\n}\n\nstatic GuestSurface* GetBackBuffer() \n{\n    g_backBuffer->AddRef();\n    return g_backBuffer;\n}\n\nstatic GuestSurface* GetDepthStencil() \n{\n    g_depthStencil->AddRef();\n    return g_depthStencil;\n}\n\nvoid Video::ComputeViewportDimensions()\n{\n    uint32_t width = g_swapChain->getWidth();\n    uint32_t height = g_swapChain->getHeight();\n    float aspectRatio = float(width) / float(height);\n\n    switch (Config::AspectRatio)\n    {\n        case EAspectRatio::Original:\n        {\n            if (aspectRatio > WIDE_ASPECT_RATIO)\n            {\n                s_viewportWidth = height * 16 / 9;\n                s_viewportHeight = height;\n            }\n            else\n            {\n                s_viewportWidth = width;\n                s_viewportHeight = width * 9 / 16;\n            }\n\n            break;\n        }\n\n        default:\n            s_viewportWidth = width;\n            s_viewportHeight = height;\n            break;\n    }\n\n    AspectRatioPatches::ComputeOffsets();\n}\n\nstatic RenderFormat ConvertFormat(uint32_t format)\n{\n    switch (format)\n    {\n    case D3DFMT_A16B16G16R16F:\n    case D3DFMT_A16B16G16R16F_2:\n    case D3DFMT_A16B16G16R16F_EXPAND:\n        return RenderFormat::R16G16B16A16_FLOAT;\n    case D3DFMT_LIN_A8R8G8B8:\n        return RenderFormat::B8G8R8A8_UNORM;\n    case D3DFMT_A8B8G8R8:\n    case D3DFMT_A8R8G8B8:\n    case D3DFMT_X8R8G8B8:\n    case D3DFMT_LE_X8R8G8B8:\n        return RenderFormat::R8G8B8A8_UNORM;\n    case D3DFMT_R32F:\n        return RenderFormat::R32_FLOAT;\n    case D3DFMT_D24FS8:\n    case D3DFMT_D24S8:\n        return RenderFormat::D32_FLOAT_S8_UINT;\n    case D3DFMT_G16R16F:\n    case D3DFMT_G16R16F_2:\n        return RenderFormat::R16G16_FLOAT;\n    case D3DFMT_INDEX16:\n        return RenderFormat::R16_UINT;\n    case D3DFMT_INDEX32:\n        return RenderFormat::R32_UINT;\n    case D3DFMT_A8:\n    case D3DFMT_L8:\n    case D3DFMT_L8_2:\n        return RenderFormat::R8_UNORM;\n    case D3DFMT_DXT1:\n        return RenderFormat::BC1_UNORM;\n    case D3DFMT_DXT4:\n        return RenderFormat::BC3_UNORM;\n    default:\n        LOGF_WARNING(\"{:x}\\n\", format);\n        assert(false && \"Unknown format\");\n        return RenderFormat::R16G16B16A16_FLOAT;\n    }\n}\n\nstatic void DiscardTexture(GuestBaseTexture* texture, RenderTextureLayout layout)\n{\n    if (g_backend == Backend::D3D12)\n    {\n        std::lock_guard lock(g_discardMutex);\n\n        g_discardCommandList->begin();\n        if (texture->layout != layout)\n        {\n            g_discardCommandList->barriers(RenderBarrierStage::GRAPHICS, RenderTextureBarrier(texture->texture, layout));\n            texture->layout = layout;\n        }\n\n        g_discardCommandList->discardTexture(texture->texture);\n        g_discardCommandList->end();\n\n        g_queue->executeCommandLists(g_discardCommandList.get(), g_discardCommandFence.get());\n        g_queue->waitForCommandFence(g_discardCommandFence.get());\n    }\n}\n\nstatic GuestTexture* CreateTexture(uint32_t width, uint32_t height, uint32_t depth, uint32_t levels, uint32_t usage, uint32_t format, uint32_t pool, uint32_t type) \n{\n    ResourceType resourceType;\n\n    switch (type)\n    {\n    case 17:\n        resourceType = ResourceType::VolumeTexture;\n        break;\n    case 19:\n        resourceType = ResourceType::ArrayTexture;\n        break;\n    default:\n        resourceType = ResourceType::Texture;\n        break;\n    }\n\n    const auto texture = g_userHeap.AllocPhysical<GuestTexture>(resourceType);\n\n    RenderTextureDesc desc;\n    desc.dimension = texture->type == ResourceType::VolumeTexture ? RenderTextureDimension::TEXTURE_3D : RenderTextureDimension::TEXTURE_2D;\n    desc.width = width;\n    desc.height = height;\n    desc.mipLevels = levels;\n    desc.format = ConvertFormat(format);\n\n    if (texture->type == ResourceType::ArrayTexture) {\n        desc.arraySize = depth;\n        desc.depth = 1;\n    } else {\n        desc.depth = depth;\n        desc.arraySize = 1;\n    }\n\n    if (RenderFormatIsDepth(desc.format))\n        desc.flags = RenderTextureFlag::DEPTH_TARGET;\n    else if (usage != 0)\n        desc.flags = RenderTextureFlag::RENDER_TARGET;\n    else\n        desc.flags = RenderTextureFlag::NONE;\n\n    texture->textureHolder = g_device->createTexture(desc);\n    texture->texture = texture->textureHolder.get();\n\n    RenderTextureViewDesc viewDesc;\n    viewDesc.format = desc.format;\n    viewDesc.dimension = texture->type == ResourceType::VolumeTexture ? RenderTextureViewDimension::TEXTURE_3D : RenderTextureViewDimension::TEXTURE_2D;\n    viewDesc.mipLevels = levels;\n\n    switch (format)\n    {\n    case D3DFMT_D24FS8:\n    case D3DFMT_D24S8:\n    case D3DFMT_L8:\n    case D3DFMT_L8_2:\n        viewDesc.componentMapping = RenderComponentMapping(RenderSwizzle::R, RenderSwizzle::R, RenderSwizzle::R, RenderSwizzle::ONE);\n        break;\n\n    case D3DFMT_X8R8G8B8:\n        viewDesc.componentMapping = RenderComponentMapping(RenderSwizzle::G, RenderSwizzle::B, RenderSwizzle::A, RenderSwizzle::ONE);\n        break;\n    }\n\n    texture->textureView = texture->texture->createTextureView(viewDesc);\n\n    texture->width = width;\n    texture->height = height;\n    texture->depth = depth;\n    texture->format = desc.format;\n    texture->mipLevels = viewDesc.mipLevels;\n    texture->viewDimension = viewDesc.dimension;\n    texture->descriptorIndex = g_textureDescriptorAllocator.allocate();\n\n    g_textureDescriptorSet->setTexture(texture->descriptorIndex, texture->texture, RenderTextureLayout::SHADER_READ, texture->textureView.get());\n\n#ifdef _DEBUG \n    texture->texture->setName(fmt::format(\"Texture {:X}\", g_memory.MapVirtual(texture)));\n#endif\n\n    if (desc.flags != RenderTextureFlag::NONE)\n    {\n        DiscardTexture(texture, desc.flags == RenderTextureFlag::RENDER_TARGET ?\n            RenderTextureLayout::COLOR_WRITE : RenderTextureLayout::DEPTH_WRITE);\n    }\n\n    // printf(\"CreateTexture: w: %d, h: %d, depth: %d, levels: %d, usage: %d, format: %d, pool: %d, type: %d - %x\\n\", width, height, depth, levels, usage, format, pool, type, texture);\n    return texture;\n}\n\nstatic RenderHeapType GetBufferHeapType()\n{\n    return g_capabilities.gpuUploadHeap ? RenderHeapType::GPU_UPLOAD : RenderHeapType::DEFAULT;\n}\n\nstatic GuestBuffer* CreateVertexBuffer(uint32_t length) \n{\n    auto buffer = g_userHeap.AllocPhysical<GuestBuffer>(ResourceType::VertexBuffer);\n    buffer->buffer = g_device->createBuffer(RenderBufferDesc::VertexBuffer(length, GetBufferHeapType(), RenderBufferFlag::INDEX));\n    buffer->dataSize = length;\n#ifdef _DEBUG \n    buffer->buffer->setName(fmt::format(\"Vertex Buffer {:X}\", g_memory.MapVirtual(buffer)));\n#endif\n    return buffer;\n}\n\nstatic GuestBuffer* CreateIndexBuffer(uint32_t length, uint32_t, uint32_t format)\n{\n    auto buffer = g_userHeap.AllocPhysical<GuestBuffer>(ResourceType::IndexBuffer);\n    buffer->buffer = g_device->createBuffer(RenderBufferDesc::IndexBuffer(length, GetBufferHeapType()));\n    buffer->dataSize = length;\n    buffer->format = ConvertFormat(format);\n    buffer->guestFormat = format;\n#ifdef _DEBUG \n    buffer->buffer->setName(fmt::format(\"Index Buffer {:X}\", g_memory.MapVirtual(buffer)));\n#endif\n    return buffer;\n}\n\nstatic std::vector<std::pair<GuestSurface*, uint32_t>> g_surfaceCache;\n\n// TODO: Singleplayer (possibly) uses the same memory location in EDRAM for HDR and FB0 surfaces,\n// so we just remember who was created first and use that instead of creating a new one.\nstatic GuestSurface* CreateSurface(uint32_t width, uint32_t height, uint32_t format, uint32_t multiSample, GuestSurfaceCreateParams* params) \n{\n    GuestSurface* surface = nullptr;\n    uint32_t baseValue = params ? params->base.get() : -1;\n    if (params) {\n        for (auto& entry : g_surfaceCache) {\n            GuestSurface* cachedSurface = entry.first;\n            uint32_t cachedBase = entry.second;\n            if (cachedSurface &&\n                cachedSurface->width == width &&\n                cachedSurface->height == height &&\n                cachedSurface->guestFormat == format &&\n                cachedBase == baseValue) {\n                surface = cachedSurface;\n                break;\n            }\n        }\n    }\n    if (!surface) {\n        // printf(\"CreateSurface: w: %d, h: %d, f: %d, ms: %d\\n\", width, height, format, multiSample);\n        RenderTextureDesc desc;\n        desc.dimension = RenderTextureDimension::TEXTURE_2D;\n        desc.width = width;\n        desc.height = height;\n        desc.depth = 1;\n        desc.mipLevels = 1;\n        desc.arraySize = 1;\n        // desc.multisampling.sampleCount = multiSample != 0 && Config::AntiAliasing != EAntiAliasing::None ? int32_t(Config::AntiAliasing.Value) : RenderSampleCount::COUNT_1;\n        if (multiSample == 0) {\n            desc.multisampling.sampleCount = RenderSampleCount::COUNT_1;\n        } else {\n            desc.multisampling.sampleCount = multiSample == 1 ? RenderSampleCount::COUNT_2 : RenderSampleCount::COUNT_4;\n        }\n        desc.format = ConvertFormat(format);\n        desc.flags = RenderFormatIsDepth(desc.format) ? RenderTextureFlag::DEPTH_TARGET : RenderTextureFlag::RENDER_TARGET;\n\n        surface = g_userHeap.AllocPhysical<GuestSurface>(RenderFormatIsDepth(desc.format) ?\n            ResourceType::DepthStencil : ResourceType::RenderTarget);\n\n        surface->textureHolder = g_device->createTexture(desc);\n        surface->texture = surface->textureHolder.get();\n        surface->width = width;\n        surface->height = height;\n        surface->format = desc.format;\n        surface->guestFormat = format;\n        surface->sampleCount = desc.multisampling.sampleCount;\n\n        RenderTextureViewDesc viewDesc;\n        viewDesc.dimension = RenderTextureViewDimension::TEXTURE_2D;\n        viewDesc.format = desc.format;\n        viewDesc.mipLevels = 1;\n        surface->textureView = surface->textureHolder->createTextureView(viewDesc);\n        surface->descriptorIndex = g_textureDescriptorAllocator.allocate();\n        g_textureDescriptorSet->setTexture(surface->descriptorIndex, surface->textureHolder.get(), RenderTextureLayout::SHADER_READ, surface->textureView.get());\n\n    #ifdef _DEBUG \n        surface->texture->setName(fmt::format(\"{} {:X}\", desc.flags & RenderTextureFlag::RENDER_TARGET ? \"Render Target\" : \"Depth Stencil\", g_memory.MapVirtual(surface)));\n    #endif\n\n        DiscardTexture(surface, desc.flags == RenderTextureFlag::RENDER_TARGET ?\n            RenderTextureLayout::COLOR_WRITE : RenderTextureLayout::DEPTH_WRITE);\n\n        if (params) {\n            surface->wasCached = true;\n            g_surfaceCache.emplace_back(surface, baseValue);\n        }\n    }\n\n    return surface;\n}\n\nstatic void FlushViewport()\n{\n    auto& commandList = g_commandLists[g_frame];\n\n    if (g_dirtyStates.viewport)\n    {\n        auto viewport = g_viewport;\n\n        // if (viewport.minDepth > viewport.maxDepth)\n        //     std::swap(viewport.minDepth, viewport.maxDepth);\n\n        commandList->setViewports(viewport);\n\n        g_dirtyStates.viewport = false;\n    }\n\n    if (g_dirtyStates.scissorRect)\n    {\n        auto scissorRect = g_scissorTestEnable ? g_scissorRect : RenderRect(\n            g_viewport.x,\n            g_viewport.y,\n            g_viewport.x + g_viewport.width,\n            g_viewport.y + g_viewport.height);\n\n        commandList->setScissors(scissorRect);\n\n        g_dirtyStates.scissorRect = false;\n    }\n}\n\nstatic void StretchRect(GuestDevice* device, uint32_t flags, uint32_t, GuestTexture* texture, uint32_t, uint32_t, uint32_t destSliceOrFace)\n{\n    // printf(\"StretchRect %x\\n\", texture);\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::StretchRect;\n    cmd.stretchRect.flags = flags;\n    cmd.stretchRect.texture = texture;\n    cmd.stretchRect.destSliceOrFace = destSliceOrFace;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void SetTextureInRenderThread(uint32_t index, GuestTexture* texture);\nstatic void SetSurface(uint32_t index, GuestSurface* surface);\n\nstatic void ProcStretchRect(const RenderCommand& cmd)\n{\n    const auto& args = cmd.stretchRect;\n\n    const bool isDepthStencil = (args.flags & 0x4) != 0;\n    const auto surface = isDepthStencil ? g_depthStencil : g_renderTarget;\n\n    // Erase previous pending command so it doesn't cause the texture to be overriden.\n    if (args.texture->sourceSurface != nullptr)\n        args.texture->sourceSurface->destinationTextures.erase(args.texture);\n\n    args.texture->sourceSurface = surface;\n    // printf(\"ProcStretchRect: surface - %x %x ? (%x : %x)\\n\", surface, isDepthStencil, g_depthStencil, g_renderTarget);\n    surface->destinationTextures.emplace(args.texture, args.destSliceOrFace);\n\n    // If the texture is assigned to any slots, set it again. This'll also push the barrier.\n    for (uint32_t i = 0; i < std::size(g_textures); i++)\n    {\n        if (g_textures[i] == args.texture)\n        {\n            // TODO: Render depth directly to slice and avoid copy\n            // Set the original texture for MSAA and surface-to-array textures as they always get resolved.\n            if (surface->sampleCount != RenderSampleCount::COUNT_1 ||\n                args.texture->type == ResourceType::ArrayTexture)\n            {\n                SetTextureInRenderThread(i, args.texture);\n                g_pendingResolves.emplace(surface);\n            }\n            else\n            {\n                SetSurface(i, surface);\n            }\n        }\n    }\n\n    // Remember to clear later.\n    g_pendingSurfaceCopies.emplace(surface);\n}\n\nstatic void SetDefaultViewport(GuestDevice* device, GuestSurface* surface)\n{\n    if (surface != nullptr)\n    {\n        RenderCommand cmd;\n        cmd.type = RenderCommandType::SetViewport;\n        cmd.setViewport.x = 0.0f;\n        cmd.setViewport.y = 0.0f;\n        cmd.setViewport.width = float(surface->width);\n        cmd.setViewport.height = float(surface->height);\n        cmd.setViewport.minDepth = 0.0f;\n        cmd.setViewport.maxDepth = 1.0f;\n        g_renderQueue.enqueue(cmd);\n\n        device->viewport.x = 0.0f;\n        device->viewport.y = 0.0f;\n        device->viewport.width = float(surface->width);\n        device->viewport.height = float(surface->height);\n        device->viewport.minZ = 0.0f;\n        device->viewport.maxZ = 1.0f;\n    }\n}\n\nstatic void SetRenderTarget(GuestDevice* device, uint32_t index, GuestSurface* renderTarget) \n{\n    if (index == 0)\n    {\n        RenderCommand cmd;\n        cmd.type = RenderCommandType::SetRenderTarget;\n        cmd.setRenderTarget.renderTarget = renderTarget;\n        g_renderQueue.enqueue(cmd);\n\n        SetDefaultViewport(device, renderTarget);\n    }\n    else\n    {\n        // Multiple targets are not currently handled. Make sure any attempt to set them is nullptr.\n        assert(renderTarget == nullptr);\n    }\n}\n\nstatic void ProcSetRenderTarget(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setRenderTarget;\n    SetDirtyValue(g_dirtyStates.renderTargetAndDepthStencil, g_renderTarget, args.renderTarget);\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.renderTargetFormat, args.renderTarget != nullptr ? args.renderTarget->format : RenderFormat::UNKNOWN);\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.sampleCount, args.renderTarget != nullptr ? args.renderTarget->sampleCount : RenderSampleCount::COUNT_1);\n\n    // When alpha to coverage is enabled, update the alpha test mode as it's dependent on sample count.\n    SetAlphaTestMode((g_pipelineState.specConstants & (SPEC_CONSTANT_ALPHA_TEST | SPEC_CONSTANT_ALPHA_TO_COVERAGE)) != 0);\n}\n\nstatic void SetDepthStencilSurface(GuestDevice* device, GuestSurface* depthStencil) \n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetDepthStencilSurface;\n    cmd.setDepthStencilSurface.depthStencil = depthStencil;\n    g_renderQueue.enqueue(cmd);\n\n    SetDefaultViewport(device, depthStencil);\n}\n\nstatic void ProcSetDepthStencilSurface(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setDepthStencilSurface;\n\n    SetDirtyValue(g_dirtyStates.renderTargetAndDepthStencil, g_depthStencil, args.depthStencil);\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.depthStencilFormat, args.depthStencil != nullptr ? args.depthStencil->format : RenderFormat::UNKNOWN);\n}\n\nstatic bool PopulateBarriersForStretchRect(GuestSurface* renderTarget, GuestSurface* depthStencil)\n{\n    bool addedAny = false;\n\n    for (const auto surface : { renderTarget, depthStencil })\n    {\n        if (surface != nullptr && !surface->destinationTextures.empty())\n        {\n            const bool multiSampling = surface->sampleCount != RenderSampleCount::COUNT_1;\n\n            RenderTextureLayout srcLayout;\n            RenderTextureLayout dstLayout;\n            bool shaderResolve = true;\n\n            if (multiSampling)\n            {\n                if (!RenderFormatIsDepth(surface->format) || g_capabilities.resolveModes)\n                {\n                    srcLayout = RenderTextureLayout::RESOLVE_SOURCE;\n                    dstLayout = RenderTextureLayout::RESOLVE_DEST;\n                    shaderResolve = false;\n                }\n            }\n\n            if (shaderResolve)\n            {\n                srcLayout = RenderTextureLayout::SHADER_READ;\n                dstLayout = (RenderFormatIsDepth(surface->format) ? RenderTextureLayout::DEPTH_WRITE : RenderTextureLayout::COLOR_WRITE);\n            }\n\n            AddBarrier(surface, srcLayout);\n\n            for (const auto [texture, _] : surface->destinationTextures)\n                AddBarrier(texture, dstLayout);\n\n            addedAny = true;\n        }\n    }\n\n    return addedAny;\n}\n\nstatic void ExecutePendingStretchRectCommands(GuestSurface* renderTarget, GuestSurface* depthStencil)\n{\n    auto& commandList = g_commandLists[g_frame];\n\n    for (const auto surface : { renderTarget, depthStencil })\n    {\n        if (surface != nullptr && !surface->destinationTextures.empty())\n        {\n            const bool multiSampling = surface->sampleCount != RenderSampleCount::COUNT_1;\n            const bool isDepthStencil = RenderFormatIsDepth(surface->format);\n\n            for (const auto [texture, slice] : surface->destinationTextures)\n            {\n                bool shaderResolve = true;\n\n                if (multiSampling)\n                {\n                    if (!isDepthStencil || g_capabilities.resolveModes)\n                    {\n                        if (isDepthStencil)\n                            commandList->resolveTextureRegion(texture->texture, 0, 0, surface->texture, nullptr, RenderResolveMode::MIN);\n                        else\n                            commandList->resolveTexture(texture->texture, surface->texture);\n\n                        shaderResolve = false;\n                    }\n                }\n\n                if (shaderResolve)\n                {\n                    RenderPipeline* pipeline = nullptr;\n\n                    if (multiSampling)\n                    {\n                        uint32_t pipelineIndex = 0;\n\n                        switch (surface->sampleCount)\n                        {\n                        case RenderSampleCount::COUNT_2:\n                            pipelineIndex = 0;\n                            break;\n                        case RenderSampleCount::COUNT_4:\n                            pipelineIndex = 1;\n                            break;\n                        case RenderSampleCount::COUNT_8:\n                            pipelineIndex = 2;\n                            break;\n                        default:\n                            assert(false && \"Unsupported MSAA sample count\");\n                            break;\n                        }\n\n                        if (isDepthStencil)\n                        {\n                            pipeline = g_resolveMsaaDepthPipelines[pipelineIndex].get();\n                        }\n                        else\n                        {\n                            auto& resolveMsaaColorPipeline = g_resolveMsaaColorPipelines[surface->format][pipelineIndex];\n                            if (resolveMsaaColorPipeline == nullptr)\n                            {\n                                RenderGraphicsPipelineDesc desc;\n                                desc.pipelineLayout = g_pipelineLayout.get();\n                                desc.vertexShader = g_copyShader.get();\n                                desc.pixelShader = g_resolveMsaaColorShaders[pipelineIndex].get();\n                                desc.renderTargetFormat[0] = texture->format;\n                                desc.renderTargetBlend[0] = RenderBlendDesc::Copy();\n                                desc.renderTargetCount = 1;\n                                resolveMsaaColorPipeline = g_device->createGraphicsPipeline(desc);\n                            }\n\n                            pipeline = resolveMsaaColorPipeline.get();\n                        }\n                    }\n                    else\n                    {\n                        if (isDepthStencil)\n                        {\n                            pipeline = g_copyDepthPipeline.get();\n                        }\n                        else\n                        {\n                            auto& copyColorPipeline = g_copyColorPipelines[texture->format];\n                            if (copyColorPipeline == nullptr)\n                            {\n                                RenderGraphicsPipelineDesc desc;\n                                desc.pipelineLayout = g_pipelineLayout.get();\n                                desc.vertexShader = g_copyShader.get();\n                                desc.pixelShader = g_copyColorShader.get();\n                                desc.renderTargetFormat[0] = texture->format;\n                                desc.renderTargetBlend[0] = RenderBlendDesc::Copy();\n                                desc.renderTargetCount = 1;\n                                copyColorPipeline = g_device->createGraphicsPipeline(desc);\n                            }\n\n                            pipeline = copyColorPipeline.get();\n                        }\n                    }\n\n                    auto& framebuffer = texture->framebuffers[slice];\n                    if (framebuffer == nullptr)\n                    {\n                        if (isDepthStencil)\n                        {\n                            RenderTextureViewDesc viewDesc;\n                            viewDesc.format = texture->format;\n                            viewDesc.dimension = texture->viewDimension;\n                            viewDesc.mipLevels = texture->mipLevels;\n                            viewDesc.arrayIndex = slice;\n                            viewDesc.arraySize = 1;\n                            auto& view = texture->framebufferViews.emplace_back(texture->texture->createTextureView(viewDesc));\n\n                            RenderFramebufferDesc desc;\n                            desc.depthAttachmentView = view.get();\n                            framebuffer = g_device->createFramebuffer(desc);\n                        }\n                        else\n                        {\n                            RenderFramebufferDesc desc;\n                            desc.colorAttachments = const_cast<const RenderTexture**>(&texture->texture);\n                            desc.colorAttachmentsCount = 1;\n                            framebuffer = g_device->createFramebuffer(desc);\n                        }\n                    }\n\n                    if (g_framebuffer != framebuffer.get())\n                    {\n                        commandList->setFramebuffer(framebuffer.get());\n                        g_framebuffer = framebuffer.get();\n                    }\n\n                    commandList->setPipeline(pipeline);\n                    commandList->setViewports(RenderViewport(0.0f, 0.0f, float(texture->width), float(texture->height), 0.0f, 1.0f));\n                    commandList->setScissors(RenderRect(0, 0, texture->width, texture->height));\n                    commandList->setGraphicsPushConstants(0, &surface->descriptorIndex, 0, sizeof(uint32_t));\n                    commandList->drawInstanced(6, 1, 0, 0);\n\n                    g_dirtyStates.renderTargetAndDepthStencil = true;\n                    g_dirtyStates.viewport = true;\n                    g_dirtyStates.pipelineState = true;\n                    g_dirtyStates.scissorRect = true;\n\n                    if (g_backend != Backend::D3D12)\n                    {\n                        g_dirtyStates.vertexShaderConstants = true; // The push constant call invalidates vertex shader constants.\n                        g_dirtyStates.depthBias = true; // Static depth bias in copy pipeline invalidates dynamic depth bias.\n                    }\n                }\n\n                texture->sourceSurface = nullptr;\n\n                // Check if any texture slots had this texture assigned, and make it point back at the original texture.\n                for (uint32_t i = 0; i < std::size(g_textures); i++)\n                {\n                    if (g_textures[i] == texture)\n                        SetTextureInRenderThread(i, texture);\n                }\n            }\n\n            surface->destinationTextures.clear();\n        }\n    }\n}\n\nstatic void ProcExecutePendingStretchRectCommands(const RenderCommand& cmd)\n{\n    bool foundAny = false;\n\n    for (const auto surface : g_pendingSurfaceCopies)\n    {\n        // Depth stencil textures in this game are guaranteed to be transient.\n        if (!RenderFormatIsDepth(surface->format))\n            foundAny |= PopulateBarriersForStretchRect(surface, nullptr);\n    }\n\n    if (foundAny)\n    {\n        FlushBarriers();\n\n        for (const auto surface : g_pendingSurfaceCopies)\n        {\n            if (!RenderFormatIsDepth(surface->format))\n                ExecutePendingStretchRectCommands(surface, nullptr);\n\n            for (const auto [texture, _] : surface->destinationTextures)\n                texture->sourceSurface = nullptr;\n\n            surface->destinationTextures.clear();\n        }\n    }\n\n    g_pendingSurfaceCopies.clear();\n    g_pendingResolves.clear();\n}\n\nstatic void SetFramebuffer(GuestSurface* renderTarget, GuestSurface* depthStencil, bool settingForClear)\n{\n    if (settingForClear || g_dirtyStates.renderTargetAndDepthStencil)\n    {\n        // printf(\"SetFramebuffer %x %x\\n\", renderTarget, depthStencil);\n        GuestSurface* framebufferContainer = nullptr;\n        RenderTexture* framebufferKey = nullptr;\n\n        if (renderTarget != nullptr && depthStencil != nullptr)\n        {\n            framebufferContainer = depthStencil; // Backbuffer texture changes per frame so we can't use the depth stencil as the key.\n            framebufferKey = renderTarget->texture;\n        }\n        else if (renderTarget != nullptr && depthStencil == nullptr)\n        {\n            framebufferContainer = renderTarget;\n            framebufferKey = renderTarget->texture; // Backbuffer texture changes per frame so we can't assume nullptr for it.\n        }\n        else if (renderTarget == nullptr && depthStencil != nullptr)\n        {\n            framebufferContainer = depthStencil;\n            framebufferKey = nullptr;\n        }\n\n        auto& commandList = g_commandLists[g_frame];\n\n        if (framebufferContainer != nullptr)\n        {\n            auto& framebuffer = framebufferContainer->framebuffers[framebufferKey];\n\n            if (framebuffer == nullptr)\n            {\n                RenderFramebufferDesc desc;\n\n                if (renderTarget != nullptr)\n                {\n                    desc.colorAttachments = const_cast<const RenderTexture**>(&renderTarget->texture);\n                    desc.colorAttachmentsCount = 1;\n                }\n\n                if (depthStencil != nullptr)\n                    desc.depthAttachment = depthStencil->texture;\n\n                framebuffer = g_device->createFramebuffer(desc);\n            }\n\n            if (g_framebuffer != framebuffer.get())\n            {\n                commandList->setFramebuffer(framebuffer.get());\n                g_framebuffer = framebuffer.get();\n            }\n        }\n        else if (g_framebuffer != nullptr)\n        {\n            commandList->setFramebuffer(nullptr);\n            g_framebuffer = nullptr;\n        }\n\n        if (g_framebuffer != nullptr)\n        {\n            SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.halfPixelOffsetX, 1.0f / float(g_framebuffer->getWidth()));\n            SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.halfPixelOffsetY, -1.0f / float(g_framebuffer->getHeight()));\n        }\n\n        g_dirtyStates.renderTargetAndDepthStencil = settingForClear;\n    }\n}\n\nstatic void Clear(GuestDevice* device, uint32_t flags, uint32_t, be<float>* color, double z, uint32_t stencil)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::Clear;\n    cmd.clear.flags = flags;\n    cmd.clear.color[0] = color[0];\n    cmd.clear.color[1] = color[1];\n    cmd.clear.color[2] = color[2];\n    cmd.clear.color[3] = color[3];\n    cmd.clear.z = float(z);\n    cmd.clear.stencil = stencil;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcClear(const RenderCommand& cmd)\n{\n    const auto& args = cmd.clear;\n\n    if (PopulateBarriersForStretchRect(g_renderTarget, g_depthStencil))\n    {\n        FlushBarriers();\n        ExecutePendingStretchRectCommands(g_renderTarget, g_depthStencil);\n    }\n\n    AddBarrier(g_renderTarget, RenderTextureLayout::COLOR_WRITE);\n    AddBarrier(g_depthStencil, RenderTextureLayout::DEPTH_WRITE);\n    FlushBarriers();\n\n    bool canClearInOnePass = (g_renderTarget == nullptr) || (g_depthStencil == nullptr) ||\n        (g_renderTarget->width == g_depthStencil->width && g_renderTarget->height == g_depthStencil->height);\n\n    if (canClearInOnePass)\n    {\n        SetFramebuffer(g_renderTarget, g_depthStencil, true);\n    }\n\n    auto& commandList = g_commandLists[g_frame];\n\n    if (g_renderTarget != nullptr && (args.flags & D3DCLEAR_TARGET) != 0)\n    {\n        if (!canClearInOnePass) {\n            SetFramebuffer(g_renderTarget, nullptr, true);\n        }\n\n        commandList->clearColor(0, RenderColor(args.color[0], args.color[1], args.color[2], args.color[3]));\n    }\n\n    const bool clearDepth = (args.flags & D3DCLEAR_ZBUFFER) != 0;\n    const bool clearStencil = (args.flags & D3DCLEAR_STENCIL) != 0;\n    if (g_depthStencil != nullptr && (clearDepth || clearStencil))\n    {\n        if (!canClearInOnePass) {\n            SetFramebuffer(nullptr, g_depthStencil, true);\n        }\n\n        commandList->clearDepthStencil(clearDepth, clearStencil, args.z, args.stencil);\n    }\n}\n\nstatic void SetViewport(GuestDevice* device, GuestViewport* viewport)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetViewport;\n    cmd.setViewport.x = viewport->x;\n    cmd.setViewport.y = viewport->y;\n    cmd.setViewport.width = viewport->width;\n    cmd.setViewport.height = viewport->height;\n    cmd.setViewport.minDepth = viewport->minZ;\n    cmd.setViewport.maxDepth = viewport->maxZ;\n    g_renderQueue.enqueue(cmd);\n\n    device->viewport.x = float(viewport->x);\n    device->viewport.y = float(viewport->y);\n    device->viewport.width = float(viewport->width);\n    device->viewport.height = float(viewport->height);\n    device->viewport.minZ = viewport->minZ;\n    device->viewport.maxZ = viewport->maxZ;\n}\n\nstatic void ProcSetViewport(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setViewport;\n\n    SetDirtyValue<float>(g_dirtyStates.viewport, g_viewport.x, args.x);\n    SetDirtyValue<float>(g_dirtyStates.viewport, g_viewport.y, args.y);\n    SetDirtyValue<float>(g_dirtyStates.viewport, g_viewport.width, args.width);\n    SetDirtyValue<float>(g_dirtyStates.viewport, g_viewport.height, args.height);\n    SetDirtyValue<float>(g_dirtyStates.viewport, g_viewport.minDepth, args.minDepth);\n    SetDirtyValue<float>(g_dirtyStates.viewport, g_viewport.maxDepth, args.maxDepth);\n    \n    uint32_t specConstants = g_pipelineState.specConstants;\n    if (args.minDepth > args.maxDepth)\n        specConstants |= SPEC_CONSTANT_REVERSE_Z;\n    else \n        specConstants &= ~SPEC_CONSTANT_REVERSE_Z;\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.specConstants, specConstants);\n\n    g_dirtyStates.scissorRect |= g_dirtyStates.viewport;\n}\n\nstatic void SetTexture(GuestDevice* device, uint32_t index, GuestTexture* texture) \n{\n    // printf(\"SetTexture: %x %d %x\\n\", device, index, texture);\n\n    if (Config::IsControllerIconsPS3() && texture != nullptr && texture->patchedTexture != nullptr)\n        texture = texture->patchedTexture.get();\n\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetTexture;\n    cmd.setTexture.index = index;\n    cmd.setTexture.texture = texture;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void SetTextureInRenderThread(uint32_t index, GuestTexture* texture)\n{\n    AddBarrier(texture, RenderTextureLayout::SHADER_READ);\n\n    auto viewDimension = texture != nullptr ? texture->viewDimension : RenderTextureViewDimension::UNKNOWN;\n\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.texture2DIndices[index],\n        viewDimension == RenderTextureViewDimension::TEXTURE_2D ? texture->descriptorIndex : TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D);\n\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.texture2DArrayIndices[index], texture != nullptr &&\n        viewDimension == RenderTextureViewDimension::TEXTURE_2D ? texture->descriptorIndex : TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY);\n\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.textureCubeIndices[index], texture != nullptr &&\n        viewDimension == RenderTextureViewDimension::TEXTURE_CUBE ? texture->descriptorIndex : TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE);\n}\n\nstatic void SetSurface(uint32_t index, GuestSurface* surface)\n{\n    AddBarrier(surface, RenderTextureLayout::SHADER_READ);\n\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.texture2DIndices[index], surface->descriptorIndex);\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.texture2DArrayIndices[index], uint32_t(TEXTURE_DESCRIPTOR_NULL_TEXTURE_2D_ARRAY));\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.textureCubeIndices[index], uint32_t(TEXTURE_DESCRIPTOR_NULL_TEXTURE_CUBE));\n}\n\nstatic void ProcSetTexture(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setTexture;\n\n    // If a pending copy operation is detected, set the source surface. The indices will be fixed later if flushing is necessary.\n    bool shouldSetTexture = true;\n    if (args.texture != nullptr && args.texture->sourceSurface != nullptr)\n    {\n        // TODO: Render depth directly to slice and avoid copy\n        // MSAA surfaces or surface-to-array need to be resolved and cannot be used directly.\n        if (args.texture->sourceSurface->sampleCount != RenderSampleCount::COUNT_1 ||\n            args.texture->type == ResourceType::ArrayTexture)\n        {\n            g_pendingResolves.emplace(args.texture->sourceSurface);\n        }\n        else\n        {\n            SetSurface(args.index, args.texture->sourceSurface);\n            shouldSetTexture = false;\n        }\n    }\n    \n    if (shouldSetTexture)\n        SetTextureInRenderThread(args.index, args.texture);\n    \n    g_textures[args.index] = args.texture;\n}\n\nstatic void SetScissorRect(GuestDevice* device, GuestRect* rect)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetScissorRect;\n    cmd.setScissorRect.top = rect->top;\n    cmd.setScissorRect.left = rect->left;\n    cmd.setScissorRect.bottom = rect->bottom;\n    cmd.setScissorRect.right = rect->right;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetScissorRect(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setScissorRect;\n\n    SetDirtyValue<int32_t>(g_dirtyStates.scissorRect, g_scissorRect.top, args.top);\n    SetDirtyValue<int32_t>(g_dirtyStates.scissorRect, g_scissorRect.left, args.left);\n    SetDirtyValue<int32_t>(g_dirtyStates.scissorRect, g_scissorRect.bottom, args.bottom);\n    SetDirtyValue<int32_t>(g_dirtyStates.scissorRect, g_scissorRect.right, args.right);\n}\n\nstatic RenderShader* GetOrLinkShader(GuestShader* guestShader, uint32_t specConstants)\n{\n    if (g_backend != Backend::D3D12 ||\n        guestShader->shaderCacheEntry == nullptr || \n        guestShader->shaderCacheEntry->specConstantsMask == 0)\n    {\n        std::lock_guard lock(guestShader->mutex);\n\n        if (guestShader->shader == nullptr)\n        {\n            assert(guestShader->shaderCacheEntry != nullptr);\n\n            switch (g_backend) {\n            case Backend::VULKAN:\n            {\n                auto compressedSpirvData = g_shaderCache.get() + guestShader->shaderCacheEntry->spirvOffset;\n\n                std::vector<uint8_t> decoded(smolv::GetDecodedBufferSize(compressedSpirvData, guestShader->shaderCacheEntry->spirvSize));\n                bool result = smolv::Decode(compressedSpirvData, guestShader->shaderCacheEntry->spirvSize, decoded.data(), decoded.size());\n                assert(result);\n\n                guestShader->shader = g_device->createShader(decoded.data(), decoded.size(), \"shaderMain\", RenderShaderFormat::SPIRV);\n                break;\n            }\n            case Backend::D3D12:\n            {\n                guestShader->shader = g_device->createShader(g_shaderCache.get() + guestShader->shaderCacheEntry->dxilOffset, \n                    guestShader->shaderCacheEntry->dxilSize, \"shaderMain\", RenderShaderFormat::DXIL);\n                break;\n            }\n            case Backend::METAL:\n            {\n                guestShader->shader = g_device->createShader(g_shaderCache.get() + guestShader->shaderCacheEntry->airOffset,\n                    guestShader->shaderCacheEntry->airSize, \"shaderMain\", RenderShaderFormat::METAL);\n                break;\n            }\n            }\n\n#ifdef _DEBUG\n            guestShader->shader->setName(fmt::format(\"{}:{:x}\", guestShader->shaderCacheEntry->filename, guestShader->shaderCacheEntry->hash));\n#endif\n        }\n\n        return guestShader->shader.get();\n    }\n\n    specConstants &= guestShader->shaderCacheEntry->specConstantsMask;\n\n    RenderShader* shader;\n    {\n        std::lock_guard lock(guestShader->mutex);\n        shader = guestShader->linkedShaders[specConstants].get();\n    }\n\n#ifdef MARATHON_RECOMP_D3D12\n    if (shader == nullptr)\n    {\n        static Mutex g_compiledSpecConstantLibraryBlobMutex;\n        static ankerl::unordered_dense::map<uint32_t, ComPtr<IDxcBlob>> g_compiledSpecConstantLibraryBlobs;\n\n        thread_local ComPtr<IDxcCompiler3> s_dxcCompiler;\n        thread_local ComPtr<IDxcLinker> s_dxcLinker;\n        thread_local ComPtr<IDxcUtils> s_dxcUtils;\n\n        wchar_t specConstantsLibName[0x100];\n        swprintf_s(specConstantsLibName, L\"SpecConstants_%d\", specConstants);\n\n        ComPtr<IDxcBlob> specConstantLibraryBlob;\n        {\n            std::lock_guard lock(g_compiledSpecConstantLibraryBlobMutex);\n            specConstantLibraryBlob = g_compiledSpecConstantLibraryBlobs[specConstants];\n        }\n\n        if (specConstantLibraryBlob == nullptr)\n        {\n            if (s_dxcCompiler == nullptr)\n            {\n                HRESULT hr = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(s_dxcCompiler.GetAddressOf()));\n                assert(SUCCEEDED(hr) && s_dxcCompiler != nullptr);\n            }\n\n            char libraryHlsl[0x100];\n            sprintf_s(libraryHlsl, \"export uint g_SpecConstants() { return %d; }\", specConstants);\n\n            DxcBuffer buffer{};\n            buffer.Ptr = libraryHlsl;\n            buffer.Size = strlen(libraryHlsl);\n\n            const wchar_t* args[1];\n            args[0] = L\"-T lib_6_3\";\n\n            ComPtr<IDxcResult> result;\n            HRESULT hr = s_dxcCompiler->Compile(&buffer, args, std::size(args), nullptr, IID_PPV_ARGS(result.GetAddressOf()));\n            assert(SUCCEEDED(hr) && result != nullptr);\n\n            hr = result->GetResult(specConstantLibraryBlob.GetAddressOf());\n            assert(SUCCEEDED(hr) && specConstantLibraryBlob != nullptr);\n\n            std::lock_guard lock(g_compiledSpecConstantLibraryBlobMutex);\n            g_compiledSpecConstantLibraryBlobs.emplace(specConstants, specConstantLibraryBlob);\n        }\n\n        if (s_dxcLinker == nullptr)\n        {\n            HRESULT hr = DxcCreateInstance(CLSID_DxcLinker, IID_PPV_ARGS(s_dxcLinker.GetAddressOf()));\n            assert(SUCCEEDED(hr) && s_dxcLinker != nullptr);\n        }\n\n        s_dxcLinker->RegisterLibrary(specConstantsLibName, specConstantLibraryBlob.Get());\n\n        wchar_t shaderLibName[0x100];\n        swprintf_s(shaderLibName, L\"Shader_%d\", guestShader->shaderCacheEntry->dxilOffset);\n\n        ComPtr<IDxcBlobEncoding> shaderLibraryBlob;\n        {\n            std::lock_guard lock(guestShader->mutex);\n            shaderLibraryBlob = guestShader->libraryBlob;\n        }\n\n        if (shaderLibraryBlob == nullptr)\n        {\n            if (s_dxcUtils == nullptr)\n            {\n                HRESULT hr = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(s_dxcUtils.GetAddressOf()));\n                assert(SUCCEEDED(hr) && s_dxcUtils != nullptr);\n            }\n\n            HRESULT hr = s_dxcUtils->CreateBlobFromPinned(\n                g_shaderCache.get() + guestShader->shaderCacheEntry->dxilOffset,\n                guestShader->shaderCacheEntry->dxilSize,\n                DXC_CP_ACP,\n                shaderLibraryBlob.GetAddressOf());\n\n            assert(SUCCEEDED(hr) && shaderLibraryBlob != nullptr);\n\n            std::lock_guard lock(guestShader->mutex);\n            guestShader->libraryBlob = shaderLibraryBlob;\n        }\n\n        s_dxcLinker->RegisterLibrary(shaderLibName, shaderLibraryBlob.Get());\n\n        const wchar_t* libraryNames[] = { specConstantsLibName, shaderLibName };\n\n        ComPtr<IDxcOperationResult> result;\n        HRESULT hr = s_dxcLinker->Link(L\"shaderMain\", guestShader->type == ResourceType::VertexShader ? L\"vs_6_0\" : L\"ps_6_0\",\n            libraryNames, std::size(libraryNames), nullptr, 0, result.GetAddressOf());\n\n        assert(SUCCEEDED(hr) && result != nullptr);\n\n        ComPtr<IDxcBlob> blob;\n        hr = result->GetResult(blob.GetAddressOf());\n        assert(SUCCEEDED(hr) && blob != nullptr);\n\n        {\n            std::lock_guard lock(guestShader->mutex);\n\n            auto& linkedShader = guestShader->linkedShaders[specConstants];\n            if (linkedShader == nullptr)\n            {\n                linkedShader = g_device->createShader(blob->GetBufferPointer(), blob->GetBufferSize(), \"shaderMain\", RenderShaderFormat::DXIL);\n                guestShader->shaderBlobs.push_back(std::move(blob));\n            }\n\n            shader = linkedShader.get();\n\n#ifdef _DEBUG\n            shader->setName(fmt::format(\"{}:{:x}\", guestShader->shaderCacheEntry->filename, guestShader->shaderCacheEntry->hash));\n#endif\n        }        \n    }\n#endif\n\n    return shader;\n}\n\nstatic void SanitizePipelineState(PipelineState& pipelineState)\n{\n    if (!pipelineState.zEnable && !pipelineState.stencilEnable)\n    {\n        pipelineState.depthStencilFormat = RenderFormat::UNKNOWN;\n    }\n\n    if (!pipelineState.zEnable)\n    {\n        pipelineState.zWriteEnable = false;\n        pipelineState.zFunc = RenderComparisonFunction::LESS;\n        pipelineState.slopeScaledDepthBias = 0.0f;\n        pipelineState.depthBias = 0;\n    }\n\n    if (!pipelineState.stencilEnable)\n    {\n        pipelineState.stencilTwoSided = false;\n        pipelineState.stencilFunc = RenderComparisonFunction::ALWAYS;\n        pipelineState.stencilFail = RenderStencilOp::KEEP;\n        pipelineState.stencilZFail = RenderStencilOp::KEEP;\n        pipelineState.stencilPass = RenderStencilOp::KEEP;\n        pipelineState.stencilMask = 0xFFFFFFFF;\n        pipelineState.stencilWriteMask = 0xFFFFFFFF;\n        pipelineState.stencilRef = 0;\n    }\n\n    if (!pipelineState.stencilTwoSided)\n    {\n        pipelineState.stencilFuncCCW = pipelineState.stencilFunc;\n        pipelineState.stencilFailCCW = pipelineState.stencilFail;\n        pipelineState.stencilZFailCCW = pipelineState.stencilZFail;\n        pipelineState.stencilPassCCW = pipelineState.stencilPass;\n    }\n\n    if (pipelineState.slopeScaledDepthBias == 0.0f)\n        pipelineState.slopeScaledDepthBias = 0.0f; // Remove sign.\n\n    if (!pipelineState.colorWriteEnable)\n    {\n        pipelineState.alphaBlendEnable = false;\n        pipelineState.renderTargetFormat = RenderFormat::UNKNOWN;\n    }\n\n    if (!pipelineState.alphaBlendEnable)\n    {\n        pipelineState.srcBlend = RenderBlend::ONE;\n        pipelineState.destBlend = RenderBlend::ZERO;\n        pipelineState.blendOp = RenderBlendOperation::ADD;\n        pipelineState.srcBlendAlpha = RenderBlend::ONE;\n        pipelineState.destBlendAlpha = RenderBlend::ZERO;\n        pipelineState.blendOpAlpha = RenderBlendOperation::ADD;\n    }\n\n    for (size_t i = 0; i < 16; i++)\n    {\n        if (!pipelineState.vertexDeclaration->vertexStreams[i])\n            pipelineState.vertexStrides[i] = 0;\n    }\n\n    uint32_t specConstantsMask = 0;\n    if (pipelineState.vertexShader->shaderCacheEntry != nullptr)\n        specConstantsMask |= pipelineState.vertexShader->shaderCacheEntry->specConstantsMask;\n\n    if (pipelineState.pixelShader != nullptr && pipelineState.pixelShader->shaderCacheEntry != nullptr)\n        specConstantsMask |= pipelineState.pixelShader->shaderCacheEntry->specConstantsMask;\n\n    pipelineState.specConstants &= specConstantsMask;\n}\n\nstatic std::unique_ptr<RenderPipeline> CreateGraphicsPipeline(const PipelineState& pipelineState)\n{\n#ifdef ASYNC_PSO_DEBUG\n    ++g_pipelinesCurrentlyCompiling;\n#endif\n\n    RenderGraphicsPipelineDesc desc;\n    desc.pipelineLayout = g_pipelineLayout.get();\n    desc.vertexShader = GetOrLinkShader(pipelineState.vertexShader, pipelineState.specConstants);\n    if (pipelineState.enableConditionalSurvey)\n        desc.pixelShader = GetOrLinkShader(g_conditionalSurveyPSShader.get(), pipelineState.specConstants);\n    else if (pipelineState.pixelShader != nullptr)\n        desc.pixelShader = GetOrLinkShader(pipelineState.pixelShader, pipelineState.specConstants);\n    else\n        desc.pixelShader = nullptr;\n    desc.depthFunction = pipelineState.zFunc;\n    desc.depthEnabled = pipelineState.zEnable;\n    desc.depthWriteEnabled = pipelineState.zWriteEnable;\n    desc.depthBias = pipelineState.depthBias;\n    desc.stencilEnabled = pipelineState.stencilEnable;\n    desc.stencilReadMask = pipelineState.stencilMask;\n    desc.stencilWriteMask = pipelineState.stencilWriteMask;\n    desc.stencilReference = pipelineState.stencilRef;\n    desc.stencilFrontFace.compareFunction = pipelineState.stencilFunc;\n    desc.stencilFrontFace.failOp = pipelineState.stencilFail;\n    desc.stencilFrontFace.depthFailOp = pipelineState.stencilZFail;\n    desc.stencilFrontFace.passOp = pipelineState.stencilPass;\n    if (pipelineState.stencilTwoSided) {\n        desc.stencilBackFace.compareFunction = pipelineState.stencilFuncCCW;\n        desc.stencilBackFace.failOp = pipelineState.stencilFailCCW;\n        desc.stencilBackFace.depthFailOp = pipelineState.stencilZFailCCW;\n        desc.stencilBackFace.passOp = pipelineState.stencilPassCCW;\n    } else {\n        desc.stencilBackFace = desc.stencilFrontFace;\n    }\n    desc.slopeScaledDepthBias = pipelineState.slopeScaledDepthBias;\n    desc.dynamicDepthBiasEnabled = g_capabilities.dynamicDepthBias;\n    desc.depthClipEnabled = true;\n    desc.primitiveTopology = pipelineState.primitiveTopology;\n    desc.cullMode = pipelineState.cullMode;\n    desc.frontFace = pipelineState.frontFace;\n    desc.renderTargetFormat[0] = pipelineState.renderTargetFormat;\n    desc.renderTargetBlend[0].blendEnabled = pipelineState.alphaBlendEnable;\n    desc.renderTargetBlend[0].srcBlend = pipelineState.srcBlend;\n    desc.renderTargetBlend[0].dstBlend = pipelineState.destBlend;\n    desc.renderTargetBlend[0].blendOp = pipelineState.blendOp;\n    desc.renderTargetBlend[0].srcBlendAlpha = pipelineState.srcBlendAlpha;\n    desc.renderTargetBlend[0].dstBlendAlpha = pipelineState.destBlendAlpha;\n    desc.renderTargetBlend[0].blendOpAlpha = pipelineState.blendOpAlpha;\n    desc.renderTargetBlend[0].renderTargetWriteMask = pipelineState.colorWriteEnable;\n    desc.renderTargetCount = pipelineState.renderTargetFormat != RenderFormat::UNKNOWN ? 1 : 0;\n    desc.depthTargetFormat = pipelineState.depthStencilFormat;\n    desc.multisampling.sampleCount = pipelineState.sampleCount;\n    desc.alphaToCoverageEnabled = pipelineState.enableAlphaToCoverage;\n    desc.inputElements = pipelineState.vertexDeclaration->inputElements.get();\n    desc.inputElementsCount = pipelineState.vertexDeclaration->inputElementCount;\n    \n    RenderSpecConstant specConstant{};\n    specConstant.value = pipelineState.specConstants;\n    \n    if (pipelineState.specConstants != 0)\n    {\n        desc.specConstants = &specConstant;\n        desc.specConstantsCount = 1;\n    }\n    \n    RenderInputSlot inputSlots[16]{};\n    uint32_t inputSlotIndices[16]{};\n    uint32_t inputSlotCount = 0;\n    \n    for (size_t i = 0; i < pipelineState.vertexDeclaration->inputElementCount; i++)\n    {\n        auto& inputElement = pipelineState.vertexDeclaration->inputElements[i];\n        auto& inputSlotIndex = inputSlotIndices[inputElement.slotIndex];\n    \n        if (inputSlotIndex == NULL)\n            inputSlotIndex = ++inputSlotCount;\n    \n        auto& inputSlot = inputSlots[inputSlotIndex - 1];\n        inputSlot.index = inputElement.slotIndex;\n        inputSlot.stride = pipelineState.vertexStrides[inputElement.slotIndex];\n        inputSlot.classification = RenderInputSlotClassification::PER_VERTEX_DATA;\n    }\n    \n    desc.inputSlots = inputSlots;\n    desc.inputSlotsCount = inputSlotCount;\n    \n    auto pipeline = g_device->createGraphicsPipeline(desc);\n\n#ifdef ASYNC_PSO_DEBUG\n    --g_pipelinesCurrentlyCompiling;\n#endif\n\n    return pipeline;\n}\n\nstatic RenderPipeline* CreateGraphicsPipelineInRenderThread(PipelineState pipelineState)\n{\n    SanitizePipelineState(pipelineState);\n\n    XXH64_hash_t hash = XXH3_64bits(&pipelineState, sizeof(pipelineState));\n    auto& pipeline = g_pipelines[hash];\n    if (pipeline == nullptr)\n    {\n        pipeline = CreateGraphicsPipeline(pipelineState);\n\n#ifdef ASYNC_PSO_DEBUG\n        bool loading = *SWA::SGlobals::ms_IsLoading;\n\n        if (loading)\n            ++g_pipelinesCreatedAsynchronously;\n        else\n            ++g_pipelinesCreatedInRenderThread;\n\n        pipeline->setName(fmt::format(\"{} {} {} {:X}\", loading ? \"ASYNC\" : \"\",\n            pipelineState.vertexShader->name, pipelineState.pixelShader != nullptr ? pipelineState.pixelShader->name : \"<none>\", hash));\n        \n        if (!loading)\n        {\n            std::lock_guard lock(g_debugMutex);\n            g_pipelineDebugText = fmt::format(\n                \"PipelineState {:X}:\\n\"\n                \"  vertexShader: {}\\n\"\n                \"  pixelShader: {}\\n\"\n                \"  vertexDeclaration: {:X}\\n\"\n                \"  zEnable: {}\\n\"\n                \"  zWriteEnable: {}\\n\"\n                \"  stencilEnable: {}\\n\"\n                \"  stencilTwoSided: {}\\n\"\n                \"  srcBlend: {}\\n\"\n                \"  destBlend: {}\\n\"\n                \"  cullMode: {}\\n\"\n                \"  frontFace: {}\\n\"\n                \"  zFunc: {}\\n\"\n                \"  stencilFunc: {}\\n\"\n                \"  stencilFail: {}\\n\"\n                \"  stencilZFail: {}\\n\"\n                \"  stencilPass: {}\\n\"\n                \"  stencilFuncCCW: {}\\n\"\n                \"  stencilFailCCW: {}\\n\"\n                \"  stencilZFailCCW: {}\\n\"\n                \"  stencilPassCCW: {}\\n\"\n                \"  stencilMask: {}\\n\"\n                \"  stencilWriteMask: {}\\n\"\n                \"  stencilRef: {}\\n\"\n                \"  alphaBlendEnable: {}\\n\"\n                \"  blendOp: {}\\n\"\n                \"  slopeScaledDepthBias: {}\\n\"\n                \"  depthBias: {}\\n\"\n                \"  srcBlendAlpha: {}\\n\"\n                \"  destBlendAlpha: {}\\n\"\n                \"  blendOpAlpha: {}\\n\"\n                \"  colorWriteEnable: {:X}\\n\"\n                \"  primitiveTopology: {}\\n\"\n                \"  vertexStrides[0]: {}\\n\"\n                \"  vertexStrides[1]: {}\\n\"\n                \"  vertexStrides[2]: {}\\n\"\n                \"  vertexStrides[3]: {}\\n\"\n                \"  renderTargetFormat: {}\\n\"\n                \"  depthStencilFormat: {}\\n\"\n                \"  sampleCount: {}\\n\"\n                \"  enableAlphaToCoverage: {}\\n\"\n                \"  enableConditionalSurvey: {}\\n\"\n                \"  specConstants: {:X}\\n\",\n                hash,\n                pipelineState.vertexShader->name,\n                pipelineState.pixelShader != nullptr ? pipelineState.pixelShader->name : \"<none>\",\n                reinterpret_cast<size_t>(pipelineState.vertexDeclaration),\n                pipelineState.zEnable,\n                pipelineState.zWriteEnable,\n                pipelineState.stencilEnable,\n                pipelineState.stencilTwoSided,\n                magic_enum::enum_name(pipelineState.srcBlend),\n                magic_enum::enum_name(pipelineState.destBlend),\n                magic_enum::enum_name(pipelineState.cullMode),\n                magic_enum::enum_name(pipelineState.frontFace),\n                magic_enum::enum_name(pipelineState.zFunc),\n                magic_enum::enum_name(pipelineState.stencilFunc),\n                magic_enum::enum_name(pipelineState.stencilFail),\n                magic_enum::enum_name(pipelineState.stencilZFail),\n                magic_enum::enum_name(pipelineState.stencilPass),\n                magic_enum::enum_name(pipelineState.stencilFuncCCW),\n                magic_enum::enum_name(pipelineState.stencilFailCCW),\n                magic_enum::enum_name(pipelineState.stencilZFailCCW),\n                magic_enum::enum_name(pipelineState.stencilPassCCW),\n                pipelineState.stencilMask,\n                pipelineState.stencilWriteMask,\n                pipelineState.stencilRef,\n                pipelineState.alphaBlendEnable,\n                magic_enum::enum_name(pipelineState.blendOp),\n                pipelineState.slopeScaledDepthBias,\n                pipelineState.depthBias,\n                magic_enum::enum_name(pipelineState.srcBlendAlpha),\n                magic_enum::enum_name(pipelineState.destBlendAlpha),\n                magic_enum::enum_name(pipelineState.blendOpAlpha),\n                pipelineState.colorWriteEnable,\n                magic_enum::enum_name(pipelineState.primitiveTopology),\n                pipelineState.vertexStrides[0],\n                pipelineState.vertexStrides[1],\n                pipelineState.vertexStrides[2],\n                pipelineState.vertexStrides[3],\n                magic_enum::enum_name(pipelineState.renderTargetFormat),\n                magic_enum::enum_name(pipelineState.depthStencilFormat),\n                pipelineState.sampleCount,\n                pipelineState.enableAlphaToCoverage,\n                pipelineState.enableConditionalSurvey,\n                pipelineState.specConstants)\n                + g_pipelineDebugText;\n        }\n#endif\n\n#ifdef PSO_CACHING\n        std::lock_guard lock(g_pipelineCacheMutex);\n        g_pipelineStatesToCache.emplace(hash, pipelineState);\n#endif\n    }\n    \n    return pipeline.get();\n}\n\nstatic RenderTextureAddressMode ConvertTextureAddressMode(size_t value)\n{\n    switch (value)\n    {\n    case D3DTADDRESS_WRAP:\n        return RenderTextureAddressMode::WRAP;\n    case D3DTADDRESS_MIRROR:\n        return RenderTextureAddressMode::MIRROR;\n    case D3DTADDRESS_CLAMP:\n        return RenderTextureAddressMode::CLAMP;\n    case D3DTADDRESS_MIRRORONCE:\n        return RenderTextureAddressMode::MIRROR_ONCE;\n    case D3DTADDRESS_BORDER:\n        return RenderTextureAddressMode::BORDER;\n    default:\n        assert(false && \"Unknown texture address mode\");\n        return RenderTextureAddressMode::UNKNOWN;\n    }\n}\n\nstatic RenderFilter ConvertTextureFilter(uint32_t value)\n{\n    switch (value)\n    {\n    case D3DTEXF_POINT:\n    case D3DTEXF_NONE:\n        return RenderFilter::NEAREST;\n    case D3DTEXF_LINEAR:\n        return RenderFilter::LINEAR;\n    default:\n        assert(false && \"Unknown texture filter\");\n        return RenderFilter::UNKNOWN;\n    }\n}\n\nstatic RenderBorderColor ConvertBorderColor(uint32_t value)\n{\n    switch (value)\n    {\n    case 0:\n        return RenderBorderColor::TRANSPARENT_BLACK;\n    case 1:\n        return RenderBorderColor::OPAQUE_WHITE;\n    default:\n        assert(false && \"Unknown border color\");\n        return RenderBorderColor::UNKNOWN;\n    }\n}\n\nstruct LocalRenderCommandQueue\n{\n    RenderCommand commands[20];\n    uint32_t count = 0;\n\n    RenderCommand& enqueue()\n    {\n        assert(count < std::size(commands));\n        return commands[count++];\n    }\n\n    void submit()\n    {\n        g_renderQueue.enqueue_bulk(commands, count);\n    }\n};\n\nstatic void FlushRenderStateForMainThread(GuestDevice* device, LocalRenderCommandQueue& queue)\n{\n    constexpr size_t BOOL_MASK = 0x2ull;\n    if ((device->dirtyFlags[3].get() & BOOL_MASK) != 0)\n    {\n        auto& cmd = queue.enqueue();\n        cmd.type = RenderCommandType::SetBooleans;\n        cmd.setBooleans.booleans = (device->vertexShaderBoolConstants[0].get() & 0xFF) | ((device->pixelShaderBoolConstants[0].get() & 0xFF) << 16);\n\n        device->dirtyFlags[3] = device->dirtyFlags[3].get() & ~BOOL_MASK;\n    }\n\n    for (uint32_t i = 0; i < 16; i++)\n    {\n        const size_t mask = 0x8000000000000000ull >> (i + 20);\n        if (device->dirtyFlags[2].get() & mask)\n        {\n            auto& cmd = queue.enqueue();\n            cmd.type = RenderCommandType::SetSamplerState;\n            cmd.setSamplerState.index = i;\n            cmd.setSamplerState.data0 = device->samplerStates[i].data[0];\n            cmd.setSamplerState.data3 = device->samplerStates[i].data[3];\n            cmd.setSamplerState.data5 = device->samplerStates[i].data[5];\n\n            device->dirtyFlags[2] = device->dirtyFlags[2].get() & ~mask;\n        }\n    }\n\n    uint64_t dirtyFlags = device->dirtyFlags[0].get();\n    if (dirtyFlags != 0)\n    {\n        int startRegister = std::countl_zero(dirtyFlags);\n        int endRegister = 64 - std::countr_zero(dirtyFlags);\n\n        uint32_t index = startRegister * 16;\n        uint32_t size = (endRegister - startRegister) * 64;\n\n        auto& cmd = queue.enqueue();\n        cmd.type = RenderCommandType::SetVertexShaderConstants;\n        cmd.setVertexShaderConstants.memory = g_intermediaryUploadAllocator.allocate(&device->vertexShaderFloatConstants[index], size);\n        cmd.setVertexShaderConstants.index = index;\n        cmd.setVertexShaderConstants.size = size;\n\n        device->dirtyFlags[0] = 0;\n    }\n\n    dirtyFlags = device->dirtyFlags[1].get();\n    if (dirtyFlags != 0)\n    {\n        int startRegister = std::countl_zero(dirtyFlags);\n        int endRegister = std::min(56, 64 - std::countr_zero(dirtyFlags));\n\n        uint32_t index = startRegister * 16;\n        uint32_t size = (endRegister - startRegister) * 64;\n\n        auto& cmd = queue.enqueue();\n        cmd.type = RenderCommandType::SetPixelShaderConstants;\n        cmd.setPixelShaderConstants.memory = g_intermediaryUploadAllocator.allocate(&device->pixelShaderFloatConstants[index], size);\n        cmd.setPixelShaderConstants.index = index;\n        cmd.setPixelShaderConstants.size = size;\n\n        device->dirtyFlags[1] = 0;\n    }\n}\n\nstatic void ProcSetBooleans(const RenderCommand& cmd)\n{\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.booleans, cmd.setBooleans.booleans);\n}\n\nstatic void ProcSetSamplerState(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setSamplerState;\n\n    const auto addressU = ConvertTextureAddressMode((args.data0 >> 10) & 0x7);\n    const auto addressV = ConvertTextureAddressMode((args.data0 >> 13) & 0x7);\n    const auto addressW = ConvertTextureAddressMode((args.data0 >> 16) & 0x7);\n    auto magFilter = ConvertTextureFilter((args.data3 >> 19) & 0x3);\n    auto minFilter = ConvertTextureFilter((args.data3 >> 21) & 0x3);\n    auto mipFilter = ConvertTextureFilter((args.data3 >> 23) & 0x3);\n    const auto borderColor = ConvertBorderColor(args.data5 & 0x3);\n\n    bool anisotropyEnabled = Config::AnisotropicFiltering > 0 && mipFilter == RenderFilter::LINEAR;\n    if (anisotropyEnabled)\n    {\n        magFilter = RenderFilter::LINEAR;\n        minFilter = RenderFilter::LINEAR;\n    }\n\n    auto& samplerDesc = g_samplerDescs[args.index];\n\n    bool dirty = false;\n\n    SetDirtyValue(dirty, samplerDesc.addressU, addressU);\n    SetDirtyValue(dirty, samplerDesc.addressV, addressV);\n    SetDirtyValue(dirty, samplerDesc.addressW, addressW);\n    SetDirtyValue(dirty, samplerDesc.minFilter, minFilter);\n    SetDirtyValue(dirty, samplerDesc.magFilter, magFilter);\n    SetDirtyValue(dirty, samplerDesc.mipmapMode, RenderMipmapMode(mipFilter));\n    SetDirtyValue(dirty, samplerDesc.maxAnisotropy, anisotropyEnabled ? Config::AnisotropicFiltering : 16u);\n    SetDirtyValue(dirty, samplerDesc.anisotropyEnabled, anisotropyEnabled);\n    SetDirtyValue(dirty, samplerDesc.borderColor, borderColor);\n\n    if (dirty)\n    {\n        auto& [descriptorIndex, sampler] = g_samplerStates[XXH3_64bits(&samplerDesc, sizeof(RenderSamplerDesc))];\n        if (descriptorIndex == NULL)\n        {\n            descriptorIndex = g_samplerStates.size();\n            sampler = g_device->createSampler(samplerDesc);\n\n            g_samplerDescriptorSet->setSampler(descriptorIndex - 1, sampler.get());\n        }\n\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.samplerIndices[args.index], descriptorIndex - 1);\n    }\n}\n\nstatic void ProcSetVertexShaderConstants(const RenderCommand& cmd)\n{\n    auto& args = cmd.setVertexShaderConstants;\n    assert((args.index * sizeof(uint32_t) + args.size) <= sizeof(g_vertexShaderConstants));\n\n    memcpy(&g_vertexShaderConstants[args.index], args.memory, args.size);\n    g_dirtyStates.vertexShaderConstants = true;\n}\n\nstatic void ProcSetPixelShaderConstants(const RenderCommand& cmd)\n{\n    auto& args = cmd.setPixelShaderConstants;\n    assert((args.index * sizeof(uint32_t) + args.size) <= sizeof(g_pixelShaderConstants));\n\n    memcpy(&g_pixelShaderConstants[args.index], args.memory, args.size);\n    g_dirtyStates.pixelShaderConstants = true;\n}\n\nstatic void ProcAddPipeline(const RenderCommand& cmd)\n{\n    auto& args = cmd.addPipeline;\n    auto& pipeline = g_pipelines[args.hash];\n\n    if (pipeline == nullptr)\n    {\n        pipeline = std::unique_ptr<RenderPipeline>(args.pipeline);\n#ifdef ASYNC_PSO_DEBUG\n        ++g_pipelinesCreatedAsynchronously;\n#endif\n    }\n    else\n    {\n#ifdef ASYNC_PSO_DEBUG\n        ++g_pipelinesDropped;\n#endif\n        delete args.pipeline;\n    }\n}\n\nstatic constexpr int32_t COMMON_DEPTH_BIAS_VALUE = int32_t((1 << 24) * 0.002f);\nstatic constexpr float COMMON_SLOPE_SCALED_DEPTH_BIAS_VALUE = 1.0f;\n\nstatic void FlushRenderStateForRenderThread()\n{\n    auto renderTarget = g_pipelineState.colorWriteEnable ? g_renderTarget : nullptr;\n    auto depthStencil = g_pipelineState.zEnable || g_pipelineState.stencilEnable ? g_depthStencil : nullptr;\n\n    bool foundAny = PopulateBarriersForStretchRect(renderTarget, depthStencil);\n\n    for (const auto surface : g_pendingResolves)\n    {\n        bool isDepthStencil = RenderFormatIsDepth(surface->format);\n        foundAny |= PopulateBarriersForStretchRect(isDepthStencil ? nullptr : surface, isDepthStencil ? surface : nullptr);\n    }\n\n    if (foundAny)\n    {\n        FlushBarriers();\n        ExecutePendingStretchRectCommands(renderTarget, depthStencil);\n\n        for (const auto surface : g_pendingResolves)\n        {\n            bool isDepthStencil = RenderFormatIsDepth(surface->format);\n            ExecutePendingStretchRectCommands(isDepthStencil ? nullptr : surface, isDepthStencil ? surface : nullptr);\n        }\n    }\n\n    if (!g_pendingResolves.empty())\n        g_pendingResolves.clear();\n\n    AddBarrier(renderTarget, RenderTextureLayout::COLOR_WRITE);\n    AddBarrier(depthStencil, RenderTextureLayout::DEPTH_WRITE);\n\n    FlushBarriers();\n\n    SetFramebuffer(renderTarget, depthStencil, false);\n    FlushViewport();\n\n    auto& commandList = g_commandLists[g_frame];\n\n    // D3D12 resets depth bias values to the pipeline values, even if they are dynamic.\n    // We can reduce unnecessary calls by making common depth bias values part of the pipeline.\n    if (g_capabilities.dynamicDepthBias && g_backend == Backend::D3D12)\n    {\n        bool useDepthBias = (g_depthBias != 0) || (g_slopeScaledDepthBias != 0.0f);\n\n        int32_t depthBias = useDepthBias ? COMMON_DEPTH_BIAS_VALUE : 0;\n        float slopeScaledDepthBias = useDepthBias ? COMMON_SLOPE_SCALED_DEPTH_BIAS_VALUE : 0.0f;\n\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.depthBias, depthBias);\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.slopeScaledDepthBias, slopeScaledDepthBias);\n    }\n\n    if (g_dirtyStates.pipelineState)\n    {\n        commandList->setPipeline(CreateGraphicsPipelineInRenderThread(g_pipelineState));\n\n        // D3D12 resets the depth bias values. Check if they need to be set again.\n        if (g_capabilities.dynamicDepthBias && g_backend == Backend::D3D12)\n            g_dirtyStates.depthBias = (g_depthBias != g_pipelineState.depthBias) || (g_slopeScaledDepthBias != g_pipelineState.slopeScaledDepthBias);\n    }\n\n    if (g_dirtyStates.depthBias && g_capabilities.dynamicDepthBias)\n        commandList->setDepthBias(g_depthBias, 0.0f, g_slopeScaledDepthBias);\n\n    if (g_dirtyStates.vertexShaderConstants)\n    {\n        auto vertexShaderConstants = g_uploadAllocators[g_frame].allocate<true>(g_vertexShaderConstants, sizeof(g_vertexShaderConstants), 0x100);\n        SetRootDescriptor(vertexShaderConstants, 0);\n    }\n\n    if (g_dirtyStates.pixelShaderConstants)\n    {\n        auto pixelShaderConstants = g_uploadAllocators[g_frame].allocate<true>(g_pixelShaderConstants, sizeof(g_pixelShaderConstants), 0x100);\n        SetRootDescriptor(pixelShaderConstants, 1);\n    }\n\n    if (g_dirtyStates.sharedConstants)\n    {\n        auto sharedConstants = g_uploadAllocators[g_frame].allocate<false>(&g_sharedConstants, sizeof(g_sharedConstants), 0x100);\n        SetRootDescriptor(sharedConstants, 2);\n    }\n\n    if (g_dirtyStates.vertexStreamFirst <= g_dirtyStates.vertexStreamLast)\n    {\n        commandList->setVertexBuffers(\n            g_dirtyStates.vertexStreamFirst,\n            g_vertexBufferViews + g_dirtyStates.vertexStreamFirst,\n            g_dirtyStates.vertexStreamLast - g_dirtyStates.vertexStreamFirst + 1,\n            g_inputSlots + g_dirtyStates.vertexStreamFirst);\n    }\n\n    if (g_dirtyStates.indices && (g_backend == Backend::D3D12 || g_indexBufferView.buffer.ref != nullptr))\n        commandList->setIndexBuffer(&g_indexBufferView);\n\n    g_dirtyStates = DirtyStates(false);\n}\n\nstatic RenderPrimitiveTopology ConvertPrimitiveType(uint32_t primitiveType)\n{\n    switch (primitiveType)\n    {\n    case D3DPT_POINTLIST:\n        return RenderPrimitiveTopology::POINT_LIST;\n    case D3DPT_LINELIST:\n        return RenderPrimitiveTopology::LINE_LIST;\n    case D3DPT_LINESTRIP:\n        return RenderPrimitiveTopology::LINE_STRIP;\n    case D3DPT_TRIANGLELIST:\n    case D3DPT_QUADLIST:\n        return RenderPrimitiveTopology::TRIANGLE_LIST;\n    case D3DPT_TRIANGLESTRIP:\n        return RenderPrimitiveTopology::TRIANGLE_STRIP;\n    case D3DPT_TRIANGLEFAN:\n        return g_capabilities.triangleFan ? RenderPrimitiveTopology::TRIANGLE_FAN : RenderPrimitiveTopology::TRIANGLE_LIST;\n    default:\n        assert(false && \"Unknown primitive type\");\n        return RenderPrimitiveTopology::UNKNOWN;\n    }\n}\n\nstatic void SetPrimitiveType(uint32_t primitiveType)\n{\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.primitiveTopology, ConvertPrimitiveType(primitiveType));\n}\n\nstatic void DrawPrimitive(GuestDevice* device, uint32_t primitiveType, uint32_t startVertex, uint32_t primitiveCount) \n{\n    LocalRenderCommandQueue queue;\n    FlushRenderStateForMainThread(device, queue);\n\n    auto& cmd = queue.enqueue();\n    cmd.type = RenderCommandType::DrawPrimitive;\n    cmd.drawPrimitive.primitiveType = primitiveType;\n    cmd.drawPrimitive.startVertex = startVertex;\n    cmd.drawPrimitive.primitiveCount = primitiveCount;\n\n    queue.submit();\n}\n\nstatic void ProcDrawPrimitive(const RenderCommand& cmd)\n{\n    const auto& args = cmd.drawPrimitive;\n\n    SetPrimitiveType(args.primitiveType);\n\n    FlushRenderStateForRenderThread();\n\n    auto& commandList = g_commandLists[g_frame];\n    commandList->drawInstanced(args.primitiveCount, 1, args.startVertex, 0);\n}\n\nstatic void DrawIndexedPrimitive(GuestDevice* device, uint32_t primitiveType, int32_t baseVertexIndex, uint32_t startIndex, uint32_t primCount)\n{\n    LocalRenderCommandQueue queue;\n    FlushRenderStateForMainThread(device, queue);\n\n    auto& cmd = queue.enqueue();\n    cmd.type = RenderCommandType::DrawIndexedPrimitive;\n    cmd.drawIndexedPrimitive.primitiveType = primitiveType;\n    cmd.drawIndexedPrimitive.baseVertexIndex = baseVertexIndex;\n    cmd.drawIndexedPrimitive.startIndex = startIndex;\n    cmd.drawIndexedPrimitive.primCount = primCount;\n\n    queue.submit();\n}\n\nstatic void ProcDrawIndexedPrimitive(const RenderCommand& cmd)\n{\n    const auto& args = cmd.drawIndexedPrimitive;\n\n    SetPrimitiveType(args.primitiveType);\n    FlushRenderStateForRenderThread();\n\n    g_commandLists[g_frame]->drawIndexedInstanced(args.primCount, 1, args.startIndex, args.baseVertexIndex, 0);\n}\n\nstatic void DrawPrimitiveUP(GuestDevice* device, uint32_t primitiveType, uint32_t primitiveCount, void* vertexStreamZeroData, uint32_t vertexStreamZeroStride)\n{\n    LocalRenderCommandQueue queue;\n    FlushRenderStateForMainThread(device, queue);\n\n    auto& cmd = queue.enqueue();\n    cmd.type = RenderCommandType::DrawPrimitiveUP;\n    cmd.drawPrimitiveUP.primitiveType = primitiveType;\n    cmd.drawPrimitiveUP.primitiveCount = primitiveCount;\n    cmd.drawPrimitiveUP.vertexStreamZeroData = g_intermediaryUploadAllocator.allocate(vertexStreamZeroData, primitiveCount * vertexStreamZeroStride);\n    cmd.drawPrimitiveUP.vertexStreamZeroSize = primitiveCount * vertexStreamZeroStride;\n    cmd.drawPrimitiveUP.vertexStreamZeroStride = vertexStreamZeroStride;\n    cmd.drawPrimitiveUP.csdFilterState = g_csdFilterState;\n    \n    queue.submit();\n}\n\nstatic void ProcDrawPrimitiveUP(const RenderCommand& cmd)\n{\n    const auto& args = cmd.drawPrimitiveUP;\n\n    SetPrimitiveType(args.primitiveType);\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.vertexStrides[0], uint8_t(args.vertexStreamZeroStride));\n\n    auto allocation = g_uploadAllocators[g_frame].allocate<true>(reinterpret_cast<const uint32_t*>(args.vertexStreamZeroData), args.vertexStreamZeroSize, 0x4);\n\n    auto& vertexBufferView = g_vertexBufferViews[0];\n    vertexBufferView.size = args.primitiveCount * args.vertexStreamZeroStride;\n    vertexBufferView.buffer = allocation.buffer->at(allocation.offset);\n    g_inputSlots[0].stride = args.vertexStreamZeroStride;\n    g_dirtyStates.vertexStreamFirst = 0;\n\n    uint32_t indexCount = 0;\n\n    if (args.primitiveType == D3DPT_QUADLIST)\n        indexCount = g_quadIndexData.prepare(args.primitiveCount);\n    else if (!g_capabilities.triangleFan && args.primitiveType == D3DPT_TRIANGLEFAN)\n        indexCount = g_triangleFanIndexData.prepare(args.primitiveCount);\n\n    if (args.csdFilterState != CsdFilterState::Unknown &&\n        (g_pipelineState.pixelShader == g_csdShader || g_pipelineState.pixelShader == g_csdFilterShader.get()))\n    {\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.pixelShader,\n            args.csdFilterState == CsdFilterState::On ? g_csdFilterShader.get() : g_csdShader);\n    }\n\n    FlushRenderStateForRenderThread();\n\n    if (indexCount != 0)\n        g_commandLists[g_frame]->drawIndexedInstanced(indexCount, 1, 0, 0, 0);\n    else\n        g_commandLists[g_frame]->drawInstanced(args.primitiveCount, 1, 0, 0);\n}\n\nstatic const char* ConvertDeclUsage(uint32_t usage)\n{\n    switch (usage)\n    {\n    case D3DDECLUSAGE_POSITION:\n        return \"POSITION\";\n    case D3DDECLUSAGE_BLENDWEIGHT:\n        return \"BLENDWEIGHT\";\n    case D3DDECLUSAGE_BLENDINDICES:\n        return \"BLENDINDICES\";\n    case D3DDECLUSAGE_NORMAL:\n        return \"NORMAL\";\n    case D3DDECLUSAGE_PSIZE:\n        return \"PSIZE\";\n    case D3DDECLUSAGE_TEXCOORD:\n        return \"TEXCOORD\";\n    case D3DDECLUSAGE_TANGENT:\n        return \"TANGENT\";\n    case D3DDECLUSAGE_BINORMAL:\n        return \"BINORMAL\";\n    case D3DDECLUSAGE_TESSFACTOR:\n        return \"TESSFACTOR\";\n    case D3DDECLUSAGE_POSITIONT:\n        return \"POSITIONT\";\n    case D3DDECLUSAGE_COLOR:\n        return \"COLOR\";\n    case D3DDECLUSAGE_FOG:\n        return \"FOG\";\n    case D3DDECLUSAGE_DEPTH:\n        return \"DEPTH\";\n    case D3DDECLUSAGE_SAMPLE:\n        return \"SAMPLE\";\n    default:\n        assert(false && \"Unknown usage\");\n        return \"UNKNOWN\";\n    }\n}\n\nstatic RenderFormat ConvertDeclType(uint32_t type)\n{\n    switch (type)\n    {\n    case D3DDECLTYPE_FLOAT1:\n        return RenderFormat::R32_FLOAT;\n    case D3DDECLTYPE_FLOAT2:\n        return RenderFormat::R32G32_FLOAT;\n    case D3DDECLTYPE_FLOAT3:\n        return RenderFormat::R32G32B32_FLOAT;\n    case D3DDECLTYPE_FLOAT4:\n        return RenderFormat::R32G32B32A32_FLOAT;\n    case D3DDECLTYPE_D3DCOLOR:\n        return RenderFormat::B8G8R8A8_UNORM;\n    case D3DDECLTYPE_UBYTE4:\n    case D3DDECLTYPE_UBYTE4_2:\n        return RenderFormat::R8G8B8A8_UINT;\n    case D3DDECLTYPE_SHORT2:\n        return RenderFormat::R16G16_SINT;\n    case D3DDECLTYPE_SHORT4:\n        return RenderFormat::R16G16B16A16_SINT;\n    case D3DDECLTYPE_UBYTE4N:\n    case D3DDECLTYPE_UBYTE4N_2:\n        return RenderFormat::R8G8B8A8_UNORM;\n    case D3DDECLTYPE_SHORT2N:\n        return RenderFormat::R16G16_SNORM;\n    case D3DDECLTYPE_SHORT4N:\n        return RenderFormat::R16G16B16A16_SNORM;\n    case D3DDECLTYPE_USHORT2N:\n        return RenderFormat::R16G16_UNORM;\n    case D3DDECLTYPE_USHORT4N:\n        return RenderFormat::R16G16B16A16_UNORM;\n    case D3DDECLTYPE_UINT1:\n        return RenderFormat::R32_UINT;\n    case D3DDECLTYPE_DEC3N_2:\n    case D3DDECLTYPE_DEC3N_3:\n        return RenderFormat::R32_UINT;\n    case D3DDECLTYPE_FLOAT16_2:\n        return RenderFormat::R16G16_FLOAT;\n    case D3DDECLTYPE_FLOAT16_4:\n        return RenderFormat::R16G16B16A16_FLOAT;\n    default:\n        assert(false && \"Unknown type\");\n        return RenderFormat::UNKNOWN;\n    }\n}\n\nstatic GuestVertexDeclaration* CreateVertexDeclarationWithoutAddRef(GuestVertexElement* vertexElements) \n{\n    size_t vertexElementCount = 0;\n    auto vertexElement = vertexElements;\n\n    while (vertexElement->stream != 0xFF && vertexElement->type != D3DDECLTYPE_UNUSED)\n    {\n        vertexElement->padding = 0;\n        ++vertexElement;\n        ++vertexElementCount;\n    }\n\n    vertexElement->padding = 0; // Clear the padding in D3DDECL_END() \n\n    std::lock_guard lock(g_vertexDeclarationMutex);\n\n    XXH64_hash_t hash = XXH3_64bits(vertexElements, vertexElementCount * sizeof(GuestVertexElement));\n    auto& vertexDeclaration = g_vertexDeclarations[hash];\n\n    if (vertexDeclaration == nullptr)\n    {\n        vertexDeclaration = g_userHeap.AllocPhysical<GuestVertexDeclaration>(ResourceType::VertexDeclaration);\n        vertexDeclaration->hash = hash;\n\n        static std::vector<RenderInputElement> inputElements;\n        inputElements.clear();\n\n        struct Location\n        {\n            uint32_t usage;\n            uint32_t usageIndex;\n            uint32_t location;\n        };\n\n        // Should match the locations defined in XenosRecomp.\n        constexpr Location locations[] =\n        {\n            { D3DDECLUSAGE_POSITION, 0, 0 },\n            { D3DDECLUSAGE_POSITION, 1, 1 },\n            { D3DDECLUSAGE_POSITION, 2, 2 },\n            { D3DDECLUSAGE_POSITION, 3, 3 },\n            { D3DDECLUSAGE_NORMAL, 0, 4 },\n            { D3DDECLUSAGE_NORMAL, 1, 5 },\n            { D3DDECLUSAGE_NORMAL, 2, 6 },\n            { D3DDECLUSAGE_NORMAL, 3, 7 },\n            { D3DDECLUSAGE_TANGENT, 0, 8 },\n            { D3DDECLUSAGE_TANGENT, 1, 9 },\n            { D3DDECLUSAGE_TANGENT, 2, 10 },\n            { D3DDECLUSAGE_TANGENT, 3, 11 },\n            { D3DDECLUSAGE_BINORMAL, 0, 12 },\n            { D3DDECLUSAGE_TEXCOORD, 0, 13 },\n            { D3DDECLUSAGE_TEXCOORD, 1, 14 },\n            { D3DDECLUSAGE_TEXCOORD, 2, 15 },\n            { D3DDECLUSAGE_TEXCOORD, 3, 16 },\n            { D3DDECLUSAGE_COLOR, 0, 17 },\n            { D3DDECLUSAGE_BLENDINDICES, 0, 18 },\n            { D3DDECLUSAGE_BLENDWEIGHT, 0, 19 },\n        };\n\n        vertexElement = vertexElements;\n        while (vertexElement->stream != 0xFF && vertexElement->type != D3DDECLTYPE_UNUSED)\n        {\n            uint32_t resolvedLocation = ~0;\n            for (auto& location : locations)\n            {\n                if (location.usage == vertexElement->usage && location.usageIndex == vertexElement->usageIndex)\n                {\n                    resolvedLocation = location.location;\n                    break;\n                }\n            }\n\n            if (resolvedLocation == ~0)\n            {\n                // Bound but not used by any guest shaders.\n                ++vertexElement;\n                continue;\n            }\n\n            auto& inputElement = inputElements.emplace_back();\n            inputElement.semanticName = ConvertDeclUsage(vertexElement->usage);\n            inputElement.semanticIndex = vertexElement->usageIndex;\n            inputElement.location = resolvedLocation;\n            inputElement.format = ConvertDeclType(vertexElement->type);\n            inputElement.slotIndex = vertexElement->stream;\n            inputElement.alignedByteOffset = vertexElement->offset;\n\n            switch (vertexElement->usage)\n            {\n            case D3DDECLUSAGE_NORMAL:\n                switch (vertexElement->type)\n                {\n                case D3DDECLTYPE_SHORT2:\n                case D3DDECLTYPE_SHORT4:\n                case D3DDECLTYPE_SHORT2N:\n                case D3DDECLTYPE_SHORT4N:\n                case D3DDECLTYPE_USHORT2N:\n                case D3DDECLTYPE_USHORT4N:\n                case D3DDECLTYPE_FLOAT16_2:\n                case D3DDECLTYPE_FLOAT16_4:\n                    vertexDeclaration->swappedNormals |= 1 << vertexElement->usageIndex;\n                    break;\n                }\n\n                break;\n            case D3DDECLUSAGE_BINORMAL:\n                switch (vertexElement->type)\n                {\n                case D3DDECLTYPE_SHORT2:\n                case D3DDECLTYPE_SHORT4:\n                case D3DDECLTYPE_SHORT2N:\n                case D3DDECLTYPE_SHORT4N:\n                case D3DDECLTYPE_USHORT2N:\n                case D3DDECLTYPE_USHORT4N:\n                case D3DDECLTYPE_FLOAT16_2:\n                case D3DDECLTYPE_FLOAT16_4:\n                    vertexDeclaration->swappedBinormals |= 1 << vertexElement->usageIndex;\n                    break;\n                }\n\n                break;\n            case D3DDECLUSAGE_TANGENT:\n                switch (vertexElement->type)\n                {\n                case D3DDECLTYPE_SHORT2:\n                case D3DDECLTYPE_SHORT4:\n                case D3DDECLTYPE_SHORT2N:\n                case D3DDECLTYPE_SHORT4N:\n                case D3DDECLTYPE_USHORT2N:\n                case D3DDECLTYPE_USHORT4N:\n                case D3DDECLTYPE_FLOAT16_2:\n                case D3DDECLTYPE_FLOAT16_4:\n                    vertexDeclaration->swappedTangents |= 1 << vertexElement->usageIndex;\n                    break;\n                }\n\n                break;\n            case D3DDECLUSAGE_BLENDWEIGHT:\n                switch (vertexElement->type)\n                {\n                case D3DDECLTYPE_SHORT2:\n                case D3DDECLTYPE_SHORT4:\n                case D3DDECLTYPE_SHORT2N:\n                case D3DDECLTYPE_SHORT4N:\n                case D3DDECLTYPE_USHORT2N:\n                case D3DDECLTYPE_USHORT4N:\n                case D3DDECLTYPE_FLOAT16_2:\n                case D3DDECLTYPE_FLOAT16_4:\n                    vertexDeclaration->swappedBlendWeights |= 1 << vertexElement->usageIndex;\n                    break;\n                }\n\n                break;\n\n            case D3DDECLUSAGE_TEXCOORD:\n                switch (vertexElement->type)\n                {\n                case D3DDECLTYPE_SHORT2:\n                case D3DDECLTYPE_SHORT4:\n                case D3DDECLTYPE_SHORT2N:\n                case D3DDECLTYPE_SHORT4N:\n                case D3DDECLTYPE_USHORT2N:\n                case D3DDECLTYPE_USHORT4N:\n                case D3DDECLTYPE_FLOAT16_2:\n                case D3DDECLTYPE_FLOAT16_4:\n                    vertexDeclaration->swappedTexcoords |= 1 << vertexElement->usageIndex;\n                    break;\n                }\n\n                break;\n            }\n\n            vertexDeclaration->vertexStreams[vertexElement->stream] = true;\n\n            ++vertexElement;\n        }\n\n        auto addInputElement = [&](uint32_t usage, uint32_t usageIndex)\n            {\n                uint32_t location = ~0;\n\n                for (auto& alsoLocation : locations)\n                {\n                    if (alsoLocation.usage == usage && alsoLocation.usageIndex == usageIndex)\n                    {\n                        location = alsoLocation.location;\n                        break;\n                    }\n                }\n\n                assert(location != ~0);\n\n                for (auto& inputElement : inputElements)\n                {\n                    if (inputElement.location == location)\n                        return;\n                }\n\n                auto format = RenderFormat::R32_FLOAT;\n                switch (usage)\n                {\n                case D3DDECLUSAGE_NORMAL:\n                case D3DDECLUSAGE_TANGENT:\n                case D3DDECLUSAGE_BINORMAL:\n                case D3DDECLUSAGE_BLENDINDICES:\n                    format = RenderFormat::R32G32B32_FLOAT;\n                    break;\n                }\n\n                inputElements.emplace_back(ConvertDeclUsage(usage), usageIndex, location, format, 15, 0);\n            };\n\n        // Assign any unbound usages to null buffer slot.\n        for (auto& location : locations)\n        {\n            addInputElement(location.usage, location.usageIndex);\n        }\n\n        vertexDeclaration->inputElements = std::make_unique<RenderInputElement[]>(inputElements.size());\n        std::copy(inputElements.begin(), inputElements.end(), vertexDeclaration->inputElements.get());\n\n        vertexDeclaration->vertexElements = std::make_unique<GuestVertexElement[]>(vertexElementCount + 1);\n        std::copy(vertexElements, vertexElements + vertexElementCount + 1, vertexDeclaration->vertexElements.get());\n\n        vertexDeclaration->inputElementCount = uint32_t(inputElements.size());\n        vertexDeclaration->vertexElementCount = vertexElementCount + 1;\n    }\n\n    vertexDeclaration->AddRef();\n    return vertexDeclaration;\n}\n\nstatic GuestVertexDeclaration* CreateVertexDeclaration(GuestVertexElement* vertexElements)\n{\n    auto vertexDeclaration = CreateVertexDeclarationWithoutAddRef(vertexElements);\n    vertexDeclaration->AddRef();\n    return vertexDeclaration;\n}\n\nstatic void SetVertexDeclaration(GuestDevice* device, GuestVertexDeclaration* vertexDeclaration) \n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetVertexDeclaration;\n    cmd.setVertexDeclaration.vertexDeclaration = vertexDeclaration;\n    g_renderQueue.enqueue(cmd);\n\n    device->vertexDeclaration = g_memory.MapVirtual(vertexDeclaration);\n}\n\nstatic void ProcSetVertexDeclaration(const RenderCommand& cmd)\n{\n    auto& args = cmd.setVertexDeclaration;\n\n    if (args.vertexDeclaration != nullptr)\n    {\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.swappedTexcoords, args.vertexDeclaration->swappedTexcoords);\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.swappedNormals, args.vertexDeclaration->swappedNormals);\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.swappedBinormals, args.vertexDeclaration->swappedBinormals);\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.swappedTangents, args.vertexDeclaration->swappedTangents);\n        SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.swappedBlendWeights, args.vertexDeclaration->swappedBlendWeights);\n\n        uint32_t specConstants = g_pipelineState.specConstants;\n        if (args.vertexDeclaration->hasR11G11B10Normal)\n            specConstants |= SPEC_CONSTANT_R11G11B10_NORMAL;\n        else\n            specConstants &= ~SPEC_CONSTANT_R11G11B10_NORMAL;\n\n        SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.specConstants, specConstants);\n    }\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.vertexDeclaration, args.vertexDeclaration);\n}\n\nstatic ShaderCacheEntry* FindShaderCacheEntry(XXH64_hash_t hash)\n{\n    auto end = g_shaderCacheEntries + g_shaderCacheEntryCount;\n    auto findResult = std::lower_bound(g_shaderCacheEntries, end, hash, [](ShaderCacheEntry& lhs, XXH64_hash_t rhs)\n        {\n            return lhs.hash < rhs;\n        });\n\n    return findResult != end && findResult->hash == hash ? findResult : nullptr;\n}\n\nstatic GuestShader* CreateShader(const be<uint32_t>* function, ResourceType resourceType)\n{\n    XXH64_hash_t hash = XXH3_64bits(function, function[1] + function[2]);\n\n    auto findResult = FindShaderCacheEntry(hash);\n    GuestShader* shader = nullptr;\n\n    if (findResult == nullptr) {\n        LOGF_WARNING(\"Shader of function {:x} is not found by value: {:x}\", reinterpret_cast<uintptr_t>(function), hash);\n        LOG_WARNING(\"Perhaps the path to the required shader will be printed before this error\");\n        __builtin_trap();\n    }\n    if (findResult != nullptr)\n    {\n        if (findResult->guestShader == nullptr)\n        {\n            shader = g_userHeap.AllocPhysical<GuestShader>(resourceType);\n\n            if (hash == 0x85ED723035ECF535)\n                shader->shader = CREATE_SHADER(blend_color_alpha_ps);\n            else if (hash == 0xB1086A4947A797DE)\n                shader->shader = CREATE_SHADER(csd_no_tex_vs);\n            else if (hash == 0xB4CAFC034A37C8A8)\n                shader->shader = CREATE_SHADER(csd_vs);\n            else\n                shader->shaderCacheEntry = findResult;\n\n            findResult->guestShader = shader;\n        }\n        else\n        {\n            shader = findResult->guestShader;\n        }\n    }\n\n    if (shader == nullptr)\n        shader = g_userHeap.AllocPhysical<GuestShader>(resourceType);\n    else\n        shader->AddRef();\n\n    if (hash == 0x31173204A896098A)\n        g_csdShader = shader;\n\n    return shader;\n}\n\nstatic GuestShader* CreateVertexShader(const be<uint32_t>* function) \n{\n    return CreateShader(function, ResourceType::VertexShader);\n}\n\nstatic void SetVertexShader(GuestDevice* device, GuestShader* shader)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetVertexShader;\n    cmd.setVertexShader.shader = shader;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetVertexShader(const RenderCommand& cmd)\n{\n    GuestShader* shader = cmd.setVertexShader.shader;\n\n    if (shader != nullptr &&\n    shader->shaderCacheEntry != nullptr)\n    {\n        if (shader->shaderCacheEntry->hash == 0x3687D038CE7D0BEA || shader->shaderCacheEntry->hash == 0xB4DA7A442DBB16CC)\n        {\n            if (Config::RadialBlur == ERadialBlur::Enhanced)\n                shader = g_enhancedBurnoutBlurVSShader.get();\n        }\n    }\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.vertexShader, shader);\n}\n\nstatic void SetStreamSource(GuestDevice* device, uint32_t index, GuestBuffer* buffer, uint32_t offset, uint32_t stride) \n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetStreamSource;\n    cmd.setStreamSource.index = index;\n    cmd.setStreamSource.buffer = buffer;\n    cmd.setStreamSource.offset = offset;\n    cmd.setStreamSource.stride = stride;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetStreamSource(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setStreamSource;\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.vertexStrides[args.index], uint8_t(args.buffer != nullptr ? args.stride : 0));\n\n    bool dirty = false;\n\n    SetDirtyValue(dirty, g_vertexBufferViews[args.index].buffer, args.buffer != nullptr ? args.buffer->buffer->at(args.offset) : RenderBufferReference{});\n    SetDirtyValue(dirty, g_vertexBufferViews[args.index].size, args.buffer != nullptr ? (args.buffer->dataSize - args.offset) : 0u);\n    SetDirtyValue(dirty, g_inputSlots[args.index].stride, args.buffer != nullptr ? args.stride : 0u);\n\n    if (dirty)\n    {\n        g_dirtyStates.vertexStreamFirst = std::min<uint8_t>(g_dirtyStates.vertexStreamFirst, args.index);\n        g_dirtyStates.vertexStreamLast = std::max<uint8_t>(g_dirtyStates.vertexStreamLast, args.index);\n    }\n}\n\nstatic void SetIndices(GuestDevice* device, GuestBuffer* buffer) \n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetIndices;\n    cmd.setIndices.buffer = buffer;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetIndices(const RenderCommand& cmd)\n{\n    const auto& args = cmd.setIndices;\n\n    SetDirtyValue(g_dirtyStates.indices, g_indexBufferView.buffer, args.buffer != nullptr ? args.buffer->buffer->at(0) : RenderBufferReference{});\n    SetDirtyValue(g_dirtyStates.indices, g_indexBufferView.format, args.buffer != nullptr ? args.buffer->format : RenderFormat::R16_UINT);\n    SetDirtyValue(g_dirtyStates.indices, g_indexBufferView.size, args.buffer != nullptr ? args.buffer->dataSize : 0u);\n}\n\nstatic GuestShader* CreatePixelShader(const be<uint32_t>* function)\n{\n    return CreateShader(function, ResourceType::PixelShader);\n}\n\nstatic void SetPixelShader(GuestDevice* device, GuestShader* shader)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetPixelShader;\n    cmd.setPixelShader.shader = shader;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetPixelShader(const RenderCommand& cmd)\n{\n    GuestShader* shader = cmd.setPixelShader.shader;\n\n    if (shader != nullptr &&\n        shader->shaderCacheEntry != nullptr)\n    {\n        if (shader->shaderCacheEntry->hash == 0xDA58F0110A8595D9 || shader->shaderCacheEntry->hash == 0x845A4EF989446C01)\n        {\n            if (Config::RadialBlur == ERadialBlur::Enhanced)\n                shader = g_enhancedBurnoutBlurPSShader.get();\n        }\n    }\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.pixelShader, shader);\n}\n\nstatic void BeginConditionalSurvey(GuestDevice* device, uint32_t index)\n{\n    assert(index < CONDITIONAL_SURVEY_MAX && \"Invalid conditional survey index.\");\n\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetConditionalSurvey;\n    cmd.setConditionalSurvey.enabled = true;\n    cmd.setConditionalSurvey.index = index;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void EndConditionalSurvey(GuestDevice* device)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetConditionalSurvey;\n    cmd.setConditionalSurvey.enabled = false;\n    cmd.setConditionalSurvey.index = 0;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetConditionalSurvey(const RenderCommand& cmd)\n{\n    if (cmd.setConditionalSurvey.enabled)\n    {\n        // Clear previous survey result first.\n        auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(sizeof(uint32_t)));\n        memset(uploadBuffer->map(), 0, sizeof(uint32_t));\n        uploadBuffer->unmap();\n\n        auto& commandList = g_commandLists[g_frame];\n        commandList->barriers(RenderBarrierStage::COPY, RenderBufferBarrier(g_conditionalSurveyBuffer.get(), RenderBufferAccess::WRITE));\n        commandList->copyBufferRegion(g_conditionalSurveyBuffer->at(cmd.setConditionalSurvey.index * sizeof(uint32_t)), uploadBuffer->at(0), sizeof(uint32_t));\n        commandList->barriers(RenderBarrierStage::GRAPHICS, RenderBufferBarrier(g_conditionalSurveyBuffer.get(), RenderBufferAccess::READ | RenderBufferAccess::WRITE));\n\n        g_tempBuffers[g_frame].emplace_back(std::move(uploadBuffer));\n    }\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.enableConditionalSurvey, cmd.setConditionalSurvey.enabled);\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.conditionalSurveyIndex, cmd.setConditionalSurvey.index);\n}\n\nstatic void BeginConditionalRendering(GuestDevice* device, uint32_t index)\n{\n    assert(index < CONDITIONAL_SURVEY_MAX && \"Invalid conditional rendering index.\");\n\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetConditionalRendering;\n    cmd.setConditionalRendering.enabled = true;\n    cmd.setConditionalRendering.index = index;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void EndConditionalRendering(GuestDevice* device)\n{\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::SetConditionalRendering;\n    cmd.setConditionalRendering.enabled = false;\n    cmd.setConditionalRendering.index = 0;\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void ProcSetConditionalRendering(const RenderCommand& cmd)\n{\n    uint32_t specConstants = g_pipelineState.specConstants;\n    if (cmd.setConditionalRendering.enabled)\n        specConstants |= SPEC_CONSTANT_CONDITIONAL_RENDERING;\n    else\n        specConstants &= ~SPEC_CONSTANT_CONDITIONAL_RENDERING;\n\n    SetDirtyValue(g_dirtyStates.pipelineState, g_pipelineState.specConstants, specConstants);\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.conditionalRenderingIndex, cmd.setConditionalRendering.index);\n}\n\nstatic void SetClipPlane(GuestDevice* device, uint32_t index, const be<float>* plane)\n{\n    if (index != 0)\n        return;\n\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.clipPlane[0], plane[0].get());\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.clipPlane[1], plane[1].get());\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.clipPlane[2], plane[2].get());\n    SetDirtyValue(g_dirtyStates.sharedConstants, g_sharedConstants.clipPlane[3], plane[3].get());\n}\n\nstatic std::thread g_renderThread([]\n    {\n#ifdef _WIN32\n        SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);\n        GuestThread::SetThreadName(GetCurrentThreadId(), \"Render Thread\");\n#endif\n\n        RenderCommand commands[32];\n\n        while (true)\n        {\n            size_t count = g_renderQueue.wait_dequeue_bulk(commands, std::size(commands));\n\n            for (size_t i = 0; i < count; i++)\n            {\n                auto& cmd = commands[i];\n                switch (cmd.type)\n                {\n                case RenderCommandType::SetRenderState:                    ProcSetRenderState(cmd); break;\n                case RenderCommandType::DestructResource:                  ProcDestructResource(cmd); break;\n                case RenderCommandType::UnlockTextureRect:                 ProcUnlockTextureRect(cmd); break;\n                case RenderCommandType::UnlockBuffer16:                    ProcUnlockBuffer16(cmd); break;\n                case RenderCommandType::UnlockBuffer32:                    ProcUnlockBuffer32(cmd); break;\n                case RenderCommandType::DrawImGui:                         ProcDrawImGui(cmd); break;\n                case RenderCommandType::ExecuteCommandList:                ProcExecuteCommandList(cmd); break;\n                case RenderCommandType::BeginCommandList:                  ProcBeginCommandList(cmd); break;\n                case RenderCommandType::StretchRect:                       ProcStretchRect(cmd); break;\n                case RenderCommandType::SetRenderTarget:                   ProcSetRenderTarget(cmd); break;\n                case RenderCommandType::SetDepthStencilSurface:            ProcSetDepthStencilSurface(cmd); break;\n                case RenderCommandType::ExecutePendingStretchRectCommands: ProcExecutePendingStretchRectCommands(cmd); break;\n                case RenderCommandType::Clear:                             ProcClear(cmd); break;\n                case RenderCommandType::SetViewport:                       ProcSetViewport(cmd); break;\n                case RenderCommandType::SetTexture:                        ProcSetTexture(cmd); break;\n                case RenderCommandType::SetScissorRect:                    ProcSetScissorRect(cmd); break;\n                case RenderCommandType::SetSamplerState:                   ProcSetSamplerState(cmd); break;\n                case RenderCommandType::SetBooleans:                       ProcSetBooleans(cmd); break;\n                case RenderCommandType::SetVertexShaderConstants:          ProcSetVertexShaderConstants(cmd); break;\n                case RenderCommandType::SetPixelShaderConstants:           ProcSetPixelShaderConstants(cmd); break;\n                case RenderCommandType::AddPipeline:                       ProcAddPipeline(cmd); break;\n                case RenderCommandType::DrawPrimitive:                     ProcDrawPrimitive(cmd); break;\n                case RenderCommandType::DrawIndexedPrimitive:              ProcDrawIndexedPrimitive(cmd); break;\n                case RenderCommandType::DrawPrimitiveUP:                   ProcDrawPrimitiveUP(cmd); break;\n                case RenderCommandType::SetVertexDeclaration:              ProcSetVertexDeclaration(cmd); break;\n                case RenderCommandType::SetVertexShader:                   ProcSetVertexShader(cmd); break;\n                case RenderCommandType::SetStreamSource:                   ProcSetStreamSource(cmd); break;\n                case RenderCommandType::SetIndices:                        ProcSetIndices(cmd); break;\n                case RenderCommandType::SetPixelShader:                    ProcSetPixelShader(cmd); break;\n                case RenderCommandType::SetConditionalSurvey:              ProcSetConditionalSurvey(cmd); break;\n                case RenderCommandType::SetConditionalRendering:           ProcSetConditionalRendering(cmd); break;\n                default:                                                   assert(false && \"Unrecognized render command type.\"); break;\n                }\n            }\n        }\n    });\n\nstruct GuestPictureData\n{\n    be<uint32_t> vtable;\n    uint8_t flags;\n    be<uint32_t> name;\n    be<uint32_t> texture;\n    be<uint32_t> type;\n};\n\nunion Bxty {\n    char _Buf[16];\n    xpointer<uint32_t> _Ptr;\n};\n\nstruct BEString\n{\n    char alval[1];\n    Bxty bx;\n    be<uint32_t> size;\n    uint32_t res;\n\n    const char* c_str() {\n        uint32_t len = size.get();\n        if (len == 0) {\n            return alval;\n        } else if (len < 16) {\n            return bx._Buf;\n        } else {\n            return reinterpret_cast<const char*>(bx._Ptr.get());\n        }\n    }\n};\n\nstruct GuestMyTexture\n{\n    be<uint32_t> vtable; // 0x0\n    be<uint32_t> field0x4; // 0x4\n    be<uint32_t> regIndex; // 0x8\n    BEString str1; // 0xC\n    BEString str2; // 0x28\n    BEString str3; // 0x44\n    be<uint32_t> byte60; // 0x60\n    be<uint32_t> texture; // 0x64\n    be<uint32_t> Surface[6]; // 0x64\n    be<uint32_t> width; // 0x80\n    be<uint32_t> height; // 0x84\n    be<uint32_t> graphicsDevice; // 0x88\n    be<uint32_t> guestDevice; // 0x8C\n};\n\nstatic RenderTextureDimension ConvertTextureDimension(ddspp::TextureType type)\n{\n    switch (type) \n    {\n    case ddspp::Texture1D:\n        return RenderTextureDimension::TEXTURE_1D;\n    case ddspp::Texture2D:\n    case ddspp::Cubemap:\n        return RenderTextureDimension::TEXTURE_2D;\n    case ddspp::Texture3D:\n        return RenderTextureDimension::TEXTURE_3D;\n    default:\n        assert(false && \"Unknown texture type from DDS.\");\n        return RenderTextureDimension::UNKNOWN;\n    }\n}\n\nstatic RenderTextureViewDimension ConvertTextureViewDimension(ddspp::TextureType type)\n{\n    switch (type)\n    {\n    case ddspp::Texture1D:\n        return RenderTextureViewDimension::TEXTURE_1D;\n    case ddspp::Texture2D:\n        return RenderTextureViewDimension::TEXTURE_2D;\n    case ddspp::Texture3D:\n        return RenderTextureViewDimension::TEXTURE_3D;\n    case ddspp::Cubemap:\n        return RenderTextureViewDimension::TEXTURE_CUBE;\n    default:\n        assert(false && \"Unknown texture type from DDS.\");\n        return RenderTextureViewDimension::UNKNOWN;\n    }\n}\n\nstatic RenderFormat ConvertDXGIFormat(ddspp::DXGIFormat format) \n{\n    switch (format)\n    {\n    case ddspp::R32G32B32A32_TYPELESS:\n        return RenderFormat::R32G32B32A32_TYPELESS;\n    case ddspp::R32G32B32A32_FLOAT:\n        return RenderFormat::R32G32B32A32_FLOAT;\n    case ddspp::R32G32B32A32_UINT:\n        return RenderFormat::R32G32B32A32_UINT;\n    case ddspp::R32G32B32A32_SINT:\n        return RenderFormat::R32G32B32A32_SINT;\n    case ddspp::R32G32B32_TYPELESS:\n        return RenderFormat::R32G32B32_TYPELESS;\n    case ddspp::R32G32B32_FLOAT:\n        return RenderFormat::R32G32B32_FLOAT;\n    case ddspp::R32G32B32_UINT:\n        return RenderFormat::R32G32B32_UINT;\n    case ddspp::R32G32B32_SINT:\n        return RenderFormat::R32G32B32_SINT;\n    case ddspp::R16G16B16A16_TYPELESS:\n        return RenderFormat::R16G16B16A16_TYPELESS;\n    case ddspp::R16G16B16A16_FLOAT:\n        return RenderFormat::R16G16B16A16_FLOAT;\n    case ddspp::R16G16B16A16_UNORM:\n        return RenderFormat::R16G16B16A16_UNORM;\n    case ddspp::R16G16B16A16_UINT:\n        return RenderFormat::R16G16B16A16_UINT;\n    case ddspp::R16G16B16A16_SNORM:\n        return RenderFormat::R16G16B16A16_SNORM;\n    case ddspp::R16G16B16A16_SINT:\n        return RenderFormat::R16G16B16A16_SINT;\n    case ddspp::R32G32_TYPELESS:\n        return RenderFormat::R32G32_TYPELESS;\n    case ddspp::R32G32_FLOAT:\n        return RenderFormat::R32G32_FLOAT;\n    case ddspp::R32G32_UINT:\n        return RenderFormat::R32G32_UINT;\n    case ddspp::R32G32_SINT:\n        return RenderFormat::R32G32_SINT;\n    case ddspp::R8G8B8A8_TYPELESS:\n        return RenderFormat::R8G8B8A8_TYPELESS;\n    case ddspp::R8G8B8A8_UNORM:\n        return RenderFormat::R8G8B8A8_UNORM;\n    case ddspp::R8G8B8A8_UINT:\n        return RenderFormat::R8G8B8A8_UINT;\n    case ddspp::R8G8B8A8_SNORM:\n        return RenderFormat::R8G8B8A8_SNORM;\n    case ddspp::R8G8B8A8_SINT:\n        return RenderFormat::R8G8B8A8_SINT;\n    case ddspp::B8G8R8A8_UNORM:\n        return RenderFormat::B8G8R8A8_UNORM;\n    case ddspp::B8G8R8X8_UNORM:\n        return RenderFormat::B8G8R8A8_UNORM;   \n    case ddspp::R16G16_TYPELESS:\n        return RenderFormat::R16G16_TYPELESS;\n    case ddspp::R16G16_FLOAT:\n        return RenderFormat::R16G16_FLOAT;\n    case ddspp::R16G16_UNORM:\n        return RenderFormat::R16G16_UNORM;\n    case ddspp::R16G16_UINT:\n        return RenderFormat::R16G16_UINT;\n    case ddspp::R16G16_SNORM:\n        return RenderFormat::R16G16_SNORM;\n    case ddspp::R16G16_SINT:\n        return RenderFormat::R16G16_SINT;\n    case ddspp::R32_TYPELESS:\n        return RenderFormat::R32_TYPELESS;\n    case ddspp::D32_FLOAT:\n        return RenderFormat::D32_FLOAT;\n    case ddspp::R32_FLOAT:\n        return RenderFormat::R32_FLOAT;\n    case ddspp::R32_UINT:\n        return RenderFormat::R32_UINT;\n    case ddspp::R32_SINT:\n        return RenderFormat::R32_SINT;\n    case ddspp::R8G8_TYPELESS:\n        return RenderFormat::R8G8_TYPELESS;\n    case ddspp::R8G8_UNORM:\n        return RenderFormat::R8G8_UNORM;\n    case ddspp::R8G8_UINT:\n        return RenderFormat::R8G8_UINT;\n    case ddspp::R8G8_SNORM:\n        return RenderFormat::R8G8_SNORM;\n    case ddspp::R8G8_SINT:\n        return RenderFormat::R8G8_SINT;\n    case ddspp::R16_TYPELESS:\n        return RenderFormat::R16_TYPELESS;\n    case ddspp::R16_FLOAT:\n        return RenderFormat::R16_FLOAT;\n    case ddspp::D16_UNORM:\n        return RenderFormat::D16_UNORM;\n    case ddspp::R16_UNORM:\n        return RenderFormat::R16_UNORM;\n    case ddspp::R16_UINT:\n        return RenderFormat::R16_UINT;\n    case ddspp::R16_SNORM:\n        return RenderFormat::R16_SNORM;\n    case ddspp::R16_SINT:\n        return RenderFormat::R16_SINT;\n    case ddspp::R8_TYPELESS:\n        return RenderFormat::R8_TYPELESS;\n    case ddspp::R8_UNORM:\n    case ddspp::A8_UNORM:\n        return RenderFormat::R8_UNORM;\n    case ddspp::R8_UINT:\n        return RenderFormat::R8_UINT;\n    case ddspp::R8_SNORM:\n        return RenderFormat::R8_SNORM;\n    case ddspp::R8_SINT:\n        return RenderFormat::R8_SINT;\n    case ddspp::BC1_TYPELESS:\n        return RenderFormat::BC1_TYPELESS;\n    case ddspp::BC1_UNORM:\n        return RenderFormat::BC1_UNORM;\n    case ddspp::BC1_UNORM_SRGB:\n        return RenderFormat::BC1_UNORM_SRGB;\n    case ddspp::BC2_TYPELESS:\n        return RenderFormat::BC2_TYPELESS;\n    case ddspp::BC2_UNORM:\n        return RenderFormat::BC2_UNORM;\n    case ddspp::BC2_UNORM_SRGB:\n        return RenderFormat::BC2_UNORM_SRGB;\n    case ddspp::BC3_TYPELESS:\n        return RenderFormat::BC3_TYPELESS;\n    case ddspp::BC3_UNORM:\n        return RenderFormat::BC3_UNORM;\n    case ddspp::BC3_UNORM_SRGB:\n        return RenderFormat::BC3_UNORM_SRGB;\n    case ddspp::BC4_TYPELESS:\n        return RenderFormat::BC4_TYPELESS;\n    case ddspp::BC4_UNORM:\n        return RenderFormat::BC4_UNORM;\n    case ddspp::BC4_SNORM:\n        return RenderFormat::BC4_SNORM;\n    case ddspp::BC5_TYPELESS:\n        return RenderFormat::BC5_TYPELESS;\n    case ddspp::BC5_UNORM:\n        return RenderFormat::BC5_UNORM;\n    case ddspp::BC5_SNORM:\n        return RenderFormat::BC5_SNORM;\n    case ddspp::BC6H_TYPELESS:\n        return RenderFormat::BC6H_TYPELESS;\n    case ddspp::BC6H_UF16:\n        return RenderFormat::BC6H_UF16;\n    case ddspp::BC6H_SF16:\n        return RenderFormat::BC6H_SF16;\n    case ddspp::BC7_TYPELESS:\n        return RenderFormat::BC7_TYPELESS;\n    case ddspp::BC7_UNORM:\n        return RenderFormat::BC7_UNORM;\n    case ddspp::BC7_UNORM_SRGB:\n        return RenderFormat::BC7_UNORM_SRGB;\n    default:\n        printf(\"format: %x\\n\", format);\n        assert(false && \"Unsupported format from DDS.\");\n        return RenderFormat::UNKNOWN;\n    }\n}\n\nstatic bool LoadTexture(GuestTexture& texture, const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)\n{\n    ddspp::Descriptor ddsDesc;\n    if (ddspp::decode_header((unsigned char *)(data), ddsDesc) != ddspp::Error)\n    {\n        RenderTextureDesc desc;\n        desc.dimension = ConvertTextureDimension(ddsDesc.type);\n        desc.width = ddsDesc.width;\n        desc.height = ddsDesc.height;\n        desc.depth = ddsDesc.depth;\n        desc.mipLevels = ddsDesc.numMips;\n        desc.arraySize = ddsDesc.type == ddspp::TextureType::Cubemap ? ddsDesc.arraySize * 6 : ddsDesc.arraySize;\n        desc.format = ConvertDXGIFormat(ddsDesc.format);\n        desc.flags = ddsDesc.type == ddspp::TextureType::Cubemap ? RenderTextureFlag::CUBE : RenderTextureFlag::NONE;\n\n        texture.textureHolder = g_device->createTexture(desc);\n        texture.texture = texture.textureHolder.get();\n        texture.layout = RenderTextureLayout::COPY_DEST;\n\n        RenderTextureViewDesc viewDesc;\n        viewDesc.format = desc.format;\n        viewDesc.dimension = ConvertTextureViewDimension(ddsDesc.type);\n        viewDesc.mipLevels = ddsDesc.numMips;\n\n        if (ddsDesc.format == ddspp::A8_UNORM)\n        {\n            // Map A8_UNORM to R8_UNORM for compatability\n            componentMapping = RenderComponentMapping(RenderSwizzle::ZERO, RenderSwizzle::ZERO, RenderSwizzle::ZERO, RenderSwizzle::R);\n        }\n\n        viewDesc.componentMapping = componentMapping;\n        texture.textureView = texture.texture->createTextureView(viewDesc);\n        texture.descriptorIndex = g_textureDescriptorAllocator.allocate();\n        g_textureDescriptorSet->setTexture(texture.descriptorIndex, texture.texture, RenderTextureLayout::SHADER_READ, texture.textureView.get());\n\n        texture.width = ddsDesc.width;\n        texture.height = ddsDesc.height;\n        texture.mipLevels = viewDesc.mipLevels;\n        texture.viewDimension = viewDesc.dimension;\n\n        struct Slice\n        {\n            uint32_t width;\n            uint32_t height;\n            uint32_t depth;\n            uint32_t srcOffset;\n            uint32_t dstOffset;\n            uint32_t srcRowPitch;\n            uint32_t dstRowPitch;\n            uint32_t rowCount;\n        };\n\n        std::vector<Slice> slices;\n        uint32_t curSrcOffset = 0;\n        uint32_t curDstOffset = 0;\n\n        for (uint32_t arraySlice = 0; arraySlice < desc.arraySize; arraySlice++)\n        {\n            for (uint32_t mipSlice = 0; mipSlice < ddsDesc.numMips; mipSlice++)\n            {\n                auto& slice = slices.emplace_back();\n\n                slice.width = std::max(1u, ddsDesc.width >> mipSlice);\n                slice.height = std::max(1u, ddsDesc.height >> mipSlice);\n                slice.depth = std::max(1u, ddsDesc.depth >> mipSlice);\n                slice.srcOffset = curSrcOffset;\n                slice.dstOffset = curDstOffset;\n                uint32_t rowPitch = ((slice.width + ddsDesc.blockWidth - 1) / ddsDesc.blockWidth) * ddsDesc.bitsPerPixelOrBlock;\n                slice.srcRowPitch = (rowPitch + 7) / 8;\n                slice.dstRowPitch = (slice.srcRowPitch + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1);\n                slice.rowCount = (slice.height + ddsDesc.blockHeight - 1) / ddsDesc.blockHeight;\n\n                curSrcOffset += slice.srcRowPitch * slice.rowCount * slice.depth;\n                curDstOffset += (slice.dstRowPitch * slice.rowCount * slice.depth + PLACEMENT_ALIGNMENT - 1) & ~(PLACEMENT_ALIGNMENT - 1);\n            }\n        }\n\n        auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(curDstOffset));\n        uint8_t* mappedMemory = reinterpret_cast<uint8_t*>(uploadBuffer->map());\n\n        for (auto& slice : slices)\n        {\n            const uint8_t* srcData = data + ddsDesc.headerSize + slice.srcOffset;\n            uint8_t* dstData = mappedMemory + slice.dstOffset;\n\n            if (slice.srcRowPitch == slice.dstRowPitch)\n            {\n                memcpy(dstData, srcData, slice.srcRowPitch * slice.rowCount * slice.depth);\n            }\n            else\n            {\n                for (size_t i = 0; i < slice.rowCount * slice.depth; i++)\n                {\n                    memcpy(dstData, srcData, slice.srcRowPitch);\n                    srcData += slice.srcRowPitch;\n                    dstData += slice.dstRowPitch;\n                }\n            }\n        }\n\n        uploadBuffer->unmap();\n\n        ExecuteCopyCommandList([&]\n            {\n                g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST));\n\n                for (size_t i = 0; i < slices.size(); i++)\n                {\n                    auto& slice = slices[i];\n\n                    g_copyCommandList->copyTextureRegion(\n                        RenderTextureCopyLocation::Subresource(texture.texture, i % desc.mipLevels, i / desc.mipLevels),\n                        RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), desc.format, slice.width, slice.height, slice.depth, (slice.dstRowPitch * 8) / ddsDesc.bitsPerPixelOrBlock * ddsDesc.blockWidth, slice.dstOffset));\n                }\n            });\n\n        return true;\n    }\n    else\n    {\n        int width, height;\n        void* stbImage = stbi_load_from_memory(data, dataSize, &width, &height, nullptr, 4);\n\n        if (stbImage != nullptr)\n        {\n            texture.textureHolder = g_device->createTexture(RenderTextureDesc::Texture2D(width, height, 1, RenderFormat::R8G8B8A8_UNORM));\n            texture.texture = texture.textureHolder.get();\n            texture.viewDimension = RenderTextureViewDimension::TEXTURE_2D;\n            texture.layout = RenderTextureLayout::COPY_DEST;\n\n            texture.descriptorIndex = g_textureDescriptorAllocator.allocate();\n            g_textureDescriptorSet->setTexture(texture.descriptorIndex, texture.texture, RenderTextureLayout::SHADER_READ);\n\n            uint32_t rowPitch = (width * 4 + PITCH_ALIGNMENT - 1) & ~(PITCH_ALIGNMENT - 1);\n            uint32_t slicePitch = rowPitch * height;\n\n            auto uploadBuffer = g_device->createBuffer(RenderBufferDesc::UploadBuffer(slicePitch));\n            uint8_t* mappedMemory = reinterpret_cast<uint8_t*>(uploadBuffer->map());\n\n            if (rowPitch == (width * 4))\n            {\n                memcpy(mappedMemory, stbImage, slicePitch);\n            }\n            else\n            {\n                auto data = reinterpret_cast<const uint8_t*>(stbImage);\n\n                for (size_t i = 0; i < height; i++)\n                {\n                    memcpy(mappedMemory, data, width * 4);\n                    data += width * 4;\n                    mappedMemory += rowPitch;\n                }\n            }\n\n            uploadBuffer->unmap();\n\n            stbi_image_free(stbImage);\n\n            ExecuteCopyCommandList([&]\n                {\n                    g_copyCommandList->barriers(RenderBarrierStage::COPY, RenderTextureBarrier(texture.texture, RenderTextureLayout::COPY_DEST));\n\n                    g_copyCommandList->copyTextureRegion(\n                        RenderTextureCopyLocation::Subresource(texture.texture, 0),\n                        RenderTextureCopyLocation::PlacedFootprint(uploadBuffer.get(), RenderFormat::R8G8B8A8_UNORM, width, height, 1, rowPitch / 4, 0));\n                });\n\n            return true;\n        }\n    }\n\n    return false;\n}\n\nstd::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping)\n{\n    GuestTexture texture(ResourceType::Texture);\n\n    if (LoadTexture(texture, data, dataSize, componentMapping))\n        return std::make_unique<GuestTexture>(std::move(texture));\n\n    return nullptr;\n}\n\nstatic void DiffPatchTexture(GuestTexture& texture, uint8_t* data, uint32_t dataSize)\n{\n    auto header = reinterpret_cast<BlockCompressionDiffPatchHeader*>(g_buttonBcDiff.get());\n    auto entries = reinterpret_cast<BlockCompressionDiffPatchEntry*>(g_buttonBcDiff.get() + header->entriesOffset);\n    auto end = entries + header->entryCount;\n    \n    auto hash = XXH3_64bits(data, dataSize);\n\n    auto findResult = std::lower_bound(entries, end, hash, [](BlockCompressionDiffPatchEntry& lhs, XXH64_hash_t rhs)\n    {\n        return lhs.hash < rhs;\n    });\n\n    if (findResult != end && findResult->hash == hash)\n    {\n        auto patch = reinterpret_cast<BlockCompressionDiffPatch*>(g_buttonBcDiff.get() + findResult->patchesOffset);\n\n        for (size_t i = 0; i < findResult->patchCount; i++)\n        {\n            assert(patch->destinationOffset + patch->patchBytesSize <= dataSize);\n            memcpy(data + patch->destinationOffset, g_buttonBcDiff.get() + patch->patchBytesOffset, patch->patchBytesSize);\n            ++patch;\n        }\n\n        GuestTexture patchedTexture(ResourceType::Texture);\n\n        if (LoadTexture(patchedTexture, data, dataSize, {}))\n            texture.patchedTexture = std::make_unique<GuestTexture>(std::move(patchedTexture));\n    }\n}\n\nstatic void MakePictureData(GuestMyTexture* pictureData, uint8_t* data, uint32_t dataSize)\n{\n    if (data != nullptr)\n    {\n        GuestTexture texture(ResourceType::Texture);\n\n        if (LoadTexture(texture, data, dataSize, {}))\n        {\n#ifdef _DEBUG\n            if (pictureData->str1.size.get() > 0)\n                texture.texture->setName(fmt::format(\"Texture {}\", pictureData->str1.c_str()));\n#endif\n            DiffPatchTexture(texture, data, dataSize);\n\n            pictureData->texture = g_memory.MapVirtual(g_userHeap.AllocPhysical<GuestTexture>(std::move(texture)));\n            pictureData->width = texture.width;\n            pictureData->height = texture.height;\n        }\n    }\n}\n\nvoid IndexBufferLengthMidAsmHook(PPCRegister& r3)\n{\n    r3.u64 *= 2;\n}\n\nvoid SetShadowResolutionMidAsmHook(PPCRegister& r11)\n{\n    auto res = (int32_t)Config::ShadowResolution.Value;\n\n    if (res > 0)\n        r11.u64 = res;\n}\n\nstatic void SetResolution(be<uint32_t>* device)\n{\n    Video::ComputeViewportDimensions();\n\n    uint32_t width = uint32_t(round(Video::s_viewportWidth * Config::ResolutionScale));\n    uint32_t height = uint32_t(round(Video::s_viewportHeight * Config::ResolutionScale));\n    device[46] = width == 0 ? 880 : width;\n    device[47] = height == 0 ? 720 : height;\n}\n\nstatic GuestShader* g_movieVertexShader;\nstatic GuestShader* g_moviePixelShaderHD;\nstatic GuestShader* g_moviePixelShaderSD;\n\nstatic int ScreenShaderInit(be<uint32_t>* a1)\n{\n    if (g_movieVertexShader == nullptr)\n    {\n        g_movieVertexShader = g_userHeap.AllocPhysical<GuestShader>(ResourceType::VertexShader);\n    }\n\n    if (g_moviePixelShaderHD == nullptr)\n    {\n        g_moviePixelShaderHD = g_userHeap.AllocPhysical<GuestShader>(ResourceType::PixelShader);\n    }\n\n    if (g_moviePixelShaderSD == nullptr)\n    {\n        g_moviePixelShaderSD = g_userHeap.AllocPhysical<GuestShader>(ResourceType::PixelShader);\n    }\n\n    g_moviePixelShaderHD->AddRef();\n    g_moviePixelShaderSD->AddRef();\n    g_movieVertexShader->AddRef();\n\n    a1[0x12] = g_memory.MapVirtual(g_movieVertexShader);\n    a1[0x51] = g_memory.MapVirtual(g_moviePixelShaderHD);\n    a1[0x52] = g_memory.MapVirtual(g_moviePixelShaderSD);\n\n    return 0;\n}\n\n// Needed for correct clearing of index buffer\nstatic bool IsSet() {\n    return true;\n}\n\nvoid MovieRendererMidAsmHook(PPCRegister& r3)\n{\n    auto device = reinterpret_cast<GuestDevice*>(g_memory.Translate(r3.u32));\n\n    // Force linear filtering & clamp addressing\n    for (size_t i = 0; i < 3; i++)\n    {\n        device->samplerStates[i].data[0] = (device->samplerStates[i].data[0].get() & ~0x7fc00) | 0x24800;\n        device->samplerStates[i].data[3] = (device->samplerStates[i].data[3].get() & ~0x1f80000) | 0x1280000;\n    }\n\n    device->dirtyFlags[3] = device->dirtyFlags[3].get() | 0xe0000000ull;\n}\n\n// Normally, we could delay setting IsMadeOne, but the game relies on that flag\n// being present to handle load priority. To work around that, we can prevent\n// IsMadeAll from being set until the compilation is finished. Time for a custom flag!\nenum\n{\n    eDatabaseDataFlags_CompilingPipelines = 0x80\n};\n\n// This is passed to pipeline compilation threads to keep the loading screen busy until \n// all of them are finished. A shared pointer makes sure the destructor is called only once.\n//struct PipelineTaskToken\n//{\n//    PipelineTaskType type{};\n//    boost::shared_ptr<Hedgehog::Database::CDatabaseData> databaseData;\n//\n//    PipelineTaskToken() : databaseData()\n//    {\n//    }\n//\n//    PipelineTaskToken(const PipelineTaskToken&) = delete;\n//\n//    PipelineTaskToken(PipelineTaskToken&& other)\n//        : type(std::exchange(other.type, PipelineTaskType::Null))\n//        , databaseData(std::exchange(other.databaseData, nullptr))\n//    {\n//    }\n//\n//    ~PipelineTaskToken()\n//    {\n//        if (type != PipelineTaskType::Null)\n//        {\n//            if (databaseData.get() != nullptr)\n//                databaseData->m_Flags &= ~eDatabaseDataFlags_CompilingPipelines;\n//\n//            if ((--g_compilingPipelineTaskCount) == 0)\n//                g_compilingPipelineTaskCount.notify_one();\n//        }\n//    }\n//};\n\n//struct PipelineStateQueueItem\n//{\n//    XXH64_hash_t pipelineHash;\n//    PipelineState pipelineState;\n//    std::shared_ptr<PipelineTaskToken> token;\n//#ifdef ASYNC_PSO_DEBUG\n//    std::string pipelineName;\n//#endif\n//};\n\n//static moodycamel::BlockingConcurrentQueue<PipelineStateQueueItem> g_pipelineStateQueue;\n\nstatic void CompilePipeline(XXH64_hash_t pipelineHash, const PipelineState& pipelineState\n#ifdef ASYNC_PSO_DEBUG\n    , const std::string& pipelineName\n#endif\n)\n{\n    auto pipeline = CreateGraphicsPipeline(pipelineState);\n#ifdef ASYNC_PSO_DEBUG\n    pipeline->setName(pipelineName);\n#endif\n\n    // Will get dropped in render thread if a different thread already managed to compile this.\n    RenderCommand cmd;\n    cmd.type = RenderCommandType::AddPipeline;\n    cmd.addPipeline.hash = pipelineHash;\n    cmd.addPipeline.pipeline = pipeline.release();\n    g_renderQueue.enqueue(cmd);\n}\n\nstatic void PipelineCompilerThread()\n{\n#ifdef _WIN32\n    int threadPriority = THREAD_PRIORITY_LOWEST;\n    SetThreadPriority(GetCurrentThread(), threadPriority);\n    GuestThread::SetThreadName(GetCurrentThreadId(), \"Pipeline Compiler Thread\");\n#endif\n\n    std::unique_ptr<GuestThreadContext> ctx;\n\n//    while (true)\n//    {\n//        PipelineStateQueueItem queueItem;\n//        g_pipelineStateQueue.wait_dequeue(queueItem);\n//\n//        if (ctx == nullptr)\n//            ctx = std::make_unique<GuestThreadContext>(0);\n//\n//#ifdef _WIN32\n//        int newThreadPriority = threadPriority;\n//\n//        bool loading = *SWA::SGlobals::ms_IsLoading;\n//        if (loading)\n//            newThreadPriority = THREAD_PRIORITY_HIGHEST;\n//        else\n//            newThreadPriority = THREAD_PRIORITY_LOWEST;\n//\n//        if (newThreadPriority != threadPriority)\n//        {\n//            SetThreadPriority(GetCurrentThread(), newThreadPriority);\n//            threadPriority = newThreadPriority;\n//        }\n//#endif\n//\n//        CompilePipeline(queueItem.pipelineHash, queueItem.pipelineState\n//#ifdef ASYNC_PSO_DEBUG\n//            , queueItem.pipelineName.c_str()\n//#endif\n//        );\n//\n//        std::this_thread::yield();\n//    }\n}\n\nstatic std::vector<std::unique_ptr<std::thread>> g_pipelineCompilerThreads = []()\n    {\n        size_t threadCount = std::max(2u, (std::thread::hardware_concurrency() * 2) / 3);\n\n        std::vector<std::unique_ptr<std::thread>> threads(threadCount);\n        for (auto& thread : threads)\n            thread = std::make_unique<std::thread>(PipelineCompilerThread);\n\n        return threads;\n    }();\n\nstatic constexpr uint32_t MODEL_DATA_VFTABLE = 0x82073A44;\nstatic constexpr uint32_t TERRAIN_MODEL_DATA_VFTABLE = 0x8211D25C;\nstatic constexpr uint32_t PARTICLE_MATERIAL_VFTABLE = 0x8211F198;\n\n// Allocate the shared pointer only when new compilations are happening.\n// If nothing was compiled, the local \"token\" variable will get destructed with RAII instead.\nstruct PipelineTaskTokenPair\n{\n//    PipelineTaskToken token;\n//    std::shared_ptr<PipelineTaskToken> sharedToken;\n};\n\n// Having this separate, because I don't want to lock a mutex in the render thread before\n// every single draw. Might be worth profiling to see if it actually has an impact and merge them.\nstatic xxHashMap<PipelineState> g_asyncPipelineStates;\n\n//static void EnqueueGraphicsPipelineCompilation(\n//    const PipelineState& pipelineState,\n//    PipelineTaskTokenPair& tokenPair,\n//    const char* name,\n//    bool isPrecompiledPipeline = false)\n//{\n//    XXH64_hash_t hash = XXH3_64bits(&pipelineState, sizeof(pipelineState));\n//    bool shouldCompile = g_asyncPipelineStates.emplace(hash, pipelineState).second;\n//\n//    if (shouldCompile)\n//    {\n//        bool loading = *SWA::SGlobals::ms_IsLoading;\n//        if (!loading && isPrecompiledPipeline)\n//        {\n//            // We can just compile here during the logos.\n//            CompilePipeline(hash, pipelineState\n//#ifdef ASYNC_PSO_DEBUG\n//                , fmt::format(\"CACHE {} {:X}\", name, hash)\n//#endif\n//            );\n//        }\n//        else\n//        {\n//            if (tokenPair.sharedToken == nullptr && tokenPair.token.type != PipelineTaskType::Null)\n//                tokenPair.sharedToken = std::make_shared<PipelineTaskToken>(std::move(tokenPair.token));\n//\n//            PipelineStateQueueItem queueItem;\n//            queueItem.pipelineHash = hash;\n//            queueItem.pipelineState = pipelineState;\n//            queueItem.token = tokenPair.sharedToken;\n//#ifdef ASYNC_PSO_DEBUG\n//            queueItem.pipelineName = fmt::format(\"ASYNC {} {:X}\", name, hash);\n//#endif\n//            g_pipelineStateQueue.enqueue(queueItem);\n//        }\n//    }\n//\n//#ifdef PSO_CACHING_CLEANUP\n//    if (shouldCompile && isPrecompiledPipeline)\n//    {\n//        std::lock_guard lock(g_pipelineCacheMutex);\n//        g_pipelineStatesToCache.emplace(hash, pipelineState);\n//    }\n//#endif\n//\n//#ifdef PSO_CACHING\n//    if (!isPrecompiledPipeline)\n//    {\n//        std::lock_guard lock(g_pipelineCacheMutex);\n//        g_pipelineStatesToCache.erase(hash);\n//    }\n//#endif\n//}\n\nstruct CompilationArgs\n{\n    PipelineTaskTokenPair tokenPair;\n    bool noGI{};\n    bool hasMoreThanOneBone{};\n    bool velocityMapQuickStep{};\n    bool objectIcon{};\n};\n\nenum class MeshLayer\n{\n    Opaque,\n    Transparent,\n    PunchThrough,\n    Special\n};\n\nstruct Mesh\n{\n    uint32_t vertexSize{};\n    uint32_t morphTargetVertexSize{};\n    GuestVertexDeclaration* vertexDeclaration{};\n//    Hedgehog::Mirage::CMaterialData* material{};\n    MeshLayer layer{};\n    bool morphModel{};\n};\n\n//static void CompileMeshPipeline(const Mesh& mesh, CompilationArgs& args)\n//{\n//    if (mesh.material == nullptr || mesh.material->m_spShaderListData.get() == nullptr)\n//        return;\n//\n//    auto& shaderList = mesh.material->m_spShaderListData;\n//\n//    bool isFur = !mesh.morphModel && !args.instancing &&\n//        strstr(shaderList->m_TypeAndName.c_str(), \"Fur\") != nullptr;\n//\n//    bool isSky = !mesh.morphModel && !args.instancing &&\n//        strstr(shaderList->m_TypeAndName.c_str(), \"Sky\") != nullptr;\n//\n//    bool isSonicMouth = !mesh.morphModel && !args.instancing &&\n//        strcmp(mesh.material->m_TypeAndName.c_str() + 2, \"sonic_gm_mouth_duble\") == 0 &&\n//        strcmp(shaderList->m_TypeAndName.c_str() + 3, \"SonicSkin_dspf[b]\") == 0;\n//\n//    bool compiledOutsideMainFramebuffer = !args.instancing && !isFur && !isSky;\n//\n//    bool constTexCoord;\n//    if (args.instancing)\n//    {\n//        constTexCoord = false;\n//    }\n//    else\n//    {\n//        constTexCoord = true;\n//        if (mesh.material->m_spTexsetData.get() != nullptr)\n//        {\n//            for (size_t i = 1; i < mesh.material->m_spTexsetData->m_TextureList.size(); i++)\n//            {\n//                if (mesh.material->m_spTexsetData->m_TextureList[i]->m_TexcoordIndex !=\n//                    mesh.material->m_spTexsetData->m_TextureList[0]->m_TexcoordIndex)\n//                {\n//                    constTexCoord = false;\n//                    break;\n//                }\n//            }\n//        }\n//    }\n//\n//    // Shadow pipeline.\n//    // if (compiledOutsideMainFramebuffer && (mesh.layer == MeshLayer::Opaque || mesh.layer == MeshLayer::PunchThrough))\n//    // {\n//    //     PipelineState pipelineState{};\n//\n//    //     if (mesh.layer == MeshLayer::PunchThrough)\n//    //     {\n//    //         pipelineState.vertexShader = FindShaderCacheEntry(0xDD4FA7BB53876300)->guestShader;\n//    //         pipelineState.pixelShader = FindShaderCacheEntry(0xE2ECA594590DDE8B)->guestShader;\n//    //     }\n//    //     else\n//    //     {\n//    //         pipelineState.vertexShader = FindShaderCacheEntry(0x8E4BB23465BD909E)->guestShader;\n//    //     }\n//\n//    //     pipelineState.vertexDeclaration = mesh.vertexDeclaration;\n//    //     pipelineState.cullMode = mesh.material->m_DoubleSided ? RenderCullMode::NONE : RenderCullMode::BACK;\n//    //     pipelineState.zFunc = RenderComparisonFunction::LESS_EQUAL;\n//\n//    //     if (g_capabilities.dynamicDepthBias)\n//    //     {\n//    //         // Put common depth bias values for reducing unnecessary calls.\n//    //         if (g_backend == Backend::D3D12)\n//    //         {\n//    //             pipelineState.depthBias = COMMON_DEPTH_BIAS_VALUE;\n//    //             pipelineState.slopeScaledDepthBias = COMMON_SLOPE_SCALED_DEPTH_BIAS_VALUE;\n//    //         }\n//    //     }\n//    //     else\n//    //     {\n//    //         pipelineState.depthBias = (1 << 24) * (*reinterpret_cast<be<float>*>(g_memory.Translate(0x83302760)));\n//    //         pipelineState.slopeScaledDepthBias = *reinterpret_cast<be<float>*>(g_memory.Translate(0x83302764));\n//    //     }\n//\n//    //     pipelineState.colorWriteEnable = 0;\n//    //     pipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_STRIP;\n//    //     pipelineState.vertexStrides[0] = mesh.vertexSize;\n//    //     pipelineState.depthStencilFormat = RenderFormat::D32_FLOAT;\n//\n//    //     if (mesh.layer == MeshLayer::PunchThrough)\n//    //         pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TEST;\n//\n//    //     const char* name = (mesh.layer == MeshLayer::PunchThrough ? \"MakeShadowMapTransparent\" : \"MakeShadowMap\");\n//    //     SanitizePipelineState(pipelineState);\n//    //     EnqueueGraphicsPipelineCompilation(pipelineState, args.tokenPair, name);\n//\n//    //     // Morph models have 4 targets where unused targets default to the first vertex stream.\n//    //     if (mesh.morphModel)\n//    //     {\n//    //         for (size_t i = 0; i < 5; i++)\n//    //         {\n//    //             for (size_t j = 0; j < 4; j++)\n//    //                 pipelineState.vertexStrides[j + 1] = i > j ? mesh.morphTargetVertexSize : mesh.vertexSize;\n//\n//    //             SanitizePipelineState(pipelineState);\n//    //             EnqueueGraphicsPipelineCompilation(pipelineState, args.tokenPair, name);\n//    //         }\n//    //     }\n//    // }\n//\n//    // // Motion blur pipeline. We could normally do the player here only, but apparently Werehog enemies also have object blur.\n//    // // TODO: Do punch through meshes get rendered?\n//    // if (!mesh.morphModel && compiledOutsideMainFramebuffer && args.hasMoreThanOneBone && mesh.layer == MeshLayer::Opaque)\n//    // {\n//    //     PipelineState pipelineState{};\n//    //     pipelineState.vertexShader = FindShaderCacheEntry(0x4620B236DC38100C)->guestShader;\n//    //     pipelineState.pixelShader = FindShaderCacheEntry(0xBBDB735BEACC8F41)->guestShader;\n//    //     pipelineState.vertexDeclaration = mesh.vertexDeclaration;\n//    //     pipelineState.cullMode = RenderCullMode::NONE;\n//    //     pipelineState.zFunc = RenderComparisonFunction::GREATER_EQUAL;\n//    //     pipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_STRIP;\n//    //     pipelineState.vertexStrides[0] = mesh.vertexSize;\n//    //     pipelineState.renderTargetFormat = RenderFormat::R8G8B8A8_UNORM;\n//    //     pipelineState.depthStencilFormat = RenderFormat::D32_FLOAT;\n//    //     pipelineState.specConstants = SPEC_CONSTANT_REVERSE_Z;\n//\n//    //     SanitizePipelineState(pipelineState);\n//    //     EnqueueGraphicsPipelineCompilation(pipelineState, args.tokenPair, \"FxVelocityMap\");\n//\n//    //     if (args.velocityMapQuickStep)\n//    //     {\n//    //         pipelineState.vertexShader = FindShaderCacheEntry(0x99DC3F27E402700D)->guestShader;\n//    //         SanitizePipelineState(pipelineState);\n//    //         EnqueueGraphicsPipelineCompilation(pipelineState, args.tokenPair, \"FxVelocityMapQuickStep\");\n//    //     }\n//    // }\n//\n//    uint32_t defaultStr = args.instancing ? 0x820C8734 : 0x8202DDBC; // \"instancing\" for instancing, \"default\" for regular\n//    guest_stack_var<Hedgehog::Base::CStringSymbol> defaultSymbol(reinterpret_cast<const char*>(g_memory.Translate(defaultStr)));\n//    auto defaultFindResult = shaderList->m_PixelShaderPermutations.find(*defaultSymbol);\n//    if (defaultFindResult == shaderList->m_PixelShaderPermutations.end())\n//        return;\n//\n//    uint32_t pixelShaderSubPermutationsToCompile = 0;\n//    if (constTexCoord) pixelShaderSubPermutationsToCompile |= 0x1;\n//    if (args.noGI) pixelShaderSubPermutationsToCompile |= 0x2;\n//\n//    if ((defaultFindResult->second.m_SubPermutations.get() & (1 << pixelShaderSubPermutationsToCompile)) == 0) pixelShaderSubPermutationsToCompile &= ~0x1;\n//    if ((defaultFindResult->second.m_SubPermutations.get() & (1 << pixelShaderSubPermutationsToCompile)) == 0) pixelShaderSubPermutationsToCompile &= ~0x2;\n//\n//    uint32_t noneStr = mesh.morphModel ? 0x820D72F0 : 0x8200D938; // \"p\" for morph, \"none\" for regular\n//    guest_stack_var<Hedgehog::Base::CStringSymbol> noneSymbol(reinterpret_cast<const char*>(g_memory.Translate(noneStr)));\n//    auto noneFindResult = defaultFindResult->second.m_VertexShaderPermutations.find(*noneSymbol);\n//    if (noneFindResult == defaultFindResult->second.m_VertexShaderPermutations.end())\n//        return;\n//\n//    uint32_t vertexShaderSubPermutationsToCompile = 0;\n//    if (constTexCoord) vertexShaderSubPermutationsToCompile |= 0x1;\n//\n//    if ((noneFindResult->second->m_SubPermutations.get() & (1 << vertexShaderSubPermutationsToCompile)) == 0)\n//        vertexShaderSubPermutationsToCompile &= ~0x1;\n//\n//    auto vertexDeclaration = mesh.vertexDeclaration;\n//    bool instancing = args.instancing || isFur;\n//\n//    if (instancing)\n//    {\n//        GuestVertexElement vertexElements[64];\n//        memcpy(vertexElements, mesh.vertexDeclaration->vertexElements.get(), (mesh.vertexDeclaration->vertexElementCount - 1) * sizeof(GuestVertexElement));\n//\n//        if (args.instancing)\n//        {\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount - 1] = { 1, 0, 0x2A23B9, 0, 5, 4 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount] = { 1, 12, 0x2C2159, 0, 5, 5 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount + 1] = { 1, 16, 0x2C2159, 0, 5, 6 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount + 2] = { 1, 20, 0x182886, 0, 10, 1 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount + 3] = { 2, 0, 0x2C82A1, 0, 0, 1 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount + 4] = D3DDECL_END();\n//        }\n//        else if (isFur)\n//        {\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount - 1] = { 1, 0, 0x2C82A1, 0, 0, 1 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount] = { 2, 0, 0x2C83A4, 0, 0, 2 };\n//            vertexElements[mesh.vertexDeclaration->vertexElementCount + 1] = D3DDECL_END();\n//        }\n//\n//        vertexDeclaration = CreateVertexDeclarationWithoutAddRef(vertexElements);\n//    }\n//\n//    for (auto& [pixelShaderSubPermutations, pixelShader] : defaultFindResult->second.m_PixelShaders)\n//    {\n//        if (pixelShader.get() == nullptr || (pixelShaderSubPermutations & 0x3) != pixelShaderSubPermutationsToCompile)\n//            continue;\n//\n//        for (auto& [vertexShaderSubPermutations, vertexShader] : noneFindResult->second->m_VertexShaders)\n//        {\n//            if (vertexShader.get() == nullptr || (vertexShaderSubPermutations & 0x1) != vertexShaderSubPermutationsToCompile)\n//                continue;\n//\n//            PipelineState pipelineState{};\n//            pipelineState.vertexShader = reinterpret_cast<GuestShader*>(vertexShader->m_spCode->m_pD3DVertexShader.get());\n//            pipelineState.pixelShader = reinterpret_cast<GuestShader*>(pixelShader->m_spCode->m_pD3DPixelShader.get());\n//            pipelineState.vertexDeclaration = vertexDeclaration;\n//            pipelineState.instancing = instancing;\n//            pipelineState.zWriteEnable = !isSky && mesh.layer != MeshLayer::Transparent;\n//            pipelineState.srcBlend = RenderBlend::SRC_ALPHA;\n//            pipelineState.destBlend = mesh.material->m_Additive ? RenderBlend::ONE : RenderBlend::INV_SRC_ALPHA;\n//            pipelineState.cullMode = mesh.material->m_DoubleSided ? RenderCullMode::NONE : RenderCullMode::BACK;\n//            pipelineState.zFunc = RenderComparisonFunction::GREATER_EQUAL; // Reverse Z\n//            pipelineState.alphaBlendEnable = mesh.layer == MeshLayer::Transparent || mesh.layer == MeshLayer::Special;\n//            pipelineState.srcBlendAlpha = RenderBlend::SRC_ALPHA;\n//            pipelineState.destBlendAlpha = RenderBlend::INV_SRC_ALPHA;\n//            pipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_STRIP;\n//            pipelineState.vertexStrides[0] = mesh.vertexSize;\n//\n//            if (args.instancing)\n//            {\n//                pipelineState.vertexStrides[1] = 24;\n//                pipelineState.vertexStrides[2] = 4;\n//            }\n//            else if (isFur)\n//            {\n//                pipelineState.vertexStrides[1] = 4;\n//                pipelineState.vertexStrides[2] = 4;\n//            }\n//\n//            pipelineState.renderTargetFormat = RenderFormat::R16G16B16A16_FLOAT;\n//            pipelineState.depthStencilFormat = RenderFormat::D32_FLOAT_S8_UINT;\n//            pipelineState.sampleCount = Config::AntiAliasing != EAntiAliasing::None ? int32_t(Config::AntiAliasing.Value) : 1;\n//\n//            if (pipelineState.vertexDeclaration->hasR11G11B10Normal)\n//                pipelineState.specConstants |= SPEC_CONSTANT_R11G11B10_NORMAL;\n//\n//            if (Config::GITextureFiltering == EGITextureFiltering::Bicubic)\n//                pipelineState.specConstants |= SPEC_CONSTANT_BICUBIC_GI_FILTER;\n//\n//            if (mesh.layer == MeshLayer::PunchThrough)\n//            {\n//                if (Config::AntiAliasing != EAntiAliasing::None && Config::TransparencyAntiAliasing)\n//                {\n//                    pipelineState.enableAlphaToCoverage = true;\n//                    pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TO_COVERAGE;\n//                }\n//                else\n//                {\n//                    pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TEST;\n//                }\n//            }\n//\n//            if (!isSky)\n//                pipelineState.specConstants |= SPEC_CONSTANT_REVERSE_Z;\n//\n//            auto createGraphicsPipeline = [&](PipelineState& pipelineStateToCreate)\n//                {\n//                    SanitizePipelineState(pipelineStateToCreate);\n//                    EnqueueGraphicsPipelineCompilation(pipelineStateToCreate, args.tokenPair, shaderList->m_TypeAndName.c_str() + 3);\n//\n//                    // Morph models have 4 targets where unused targets default to the first vertex stream.\n//                    if (mesh.morphModel)\n//                    {\n//                        for (size_t i = 0; i < 5; i++)\n//                        {\n//                            for (size_t j = 0; j < 4; j++)\n//                                pipelineStateToCreate.vertexStrides[j + 1] = i > j ? mesh.morphTargetVertexSize : mesh.vertexSize;\n//\n//                            SanitizePipelineState(pipelineStateToCreate);\n//                            EnqueueGraphicsPipelineCompilation(pipelineStateToCreate, args.tokenPair, shaderList->m_TypeAndName.c_str() + 3);\n//                        }\n//                    }\n//                };\n//\n//            createGraphicsPipeline(pipelineState);\n//\n//            // We cannot rely on this being accurate during loading as SceneEffect.prm.xml gets loaded a bit later.\n//            bool planarReflectionEnabled = *reinterpret_cast<bool*>(g_memory.Translate(0x832FA0D8));\n//            bool loading = *SWA::SGlobals::ms_IsLoading;\n//            bool compileNoMsaaPipeline = pipelineState.sampleCount != 1 && (loading || planarReflectionEnabled);\n//\n//            auto noMsaaPipeline = pipelineState;\n//            noMsaaPipeline.sampleCount = 1;\n//            noMsaaPipeline.enableAlphaToCoverage = false;\n//\n//            if ((noMsaaPipeline.specConstants & SPEC_CONSTANT_ALPHA_TO_COVERAGE) != 0)\n//            {\n//                noMsaaPipeline.specConstants &= ~SPEC_CONSTANT_ALPHA_TO_COVERAGE;\n//                noMsaaPipeline.specConstants |= SPEC_CONSTANT_ALPHA_TEST;\n//            }\n//\n//            if (compileNoMsaaPipeline)\n//            {\n//                // Planar reflections don't use MSAA.\n//                createGraphicsPipeline(noMsaaPipeline);\n//            }\n//\n//            if (args.objectIcon)\n//            {\n//                // Object icons get rendered to a SDR buffer without MSAA.\n//                auto iconPipelineState = noMsaaPipeline;\n//                iconPipelineState.renderTargetFormat = RenderFormat::R8G8B8A8_UNORM;\n//                createGraphicsPipeline(iconPipelineState);\n//            }\n//\n//            if (isSonicMouth)\n//            {\n//                // Sonic's mouth switches between \"SonicSkin_dspf[b]\" or \"SonicSkinNodeInvX_dspf[b]\" depending on the view angle.\n//                auto mouthPipelineState = pipelineState;\n//                mouthPipelineState.vertexShader = FindShaderCacheEntry(0x689AA3140AB9EBAA)->guestShader;\n//                createGraphicsPipeline(mouthPipelineState);\n//\n//                if (compileNoMsaaPipeline)\n//                {\n//                    auto noMsaaMouthPipelineState = noMsaaPipeline;\n//                    noMsaaMouthPipelineState.vertexShader = mouthPipelineState.vertexShader;\n//                    createGraphicsPipeline(noMsaaMouthPipelineState);\n//                }\n//            }\n//        }\n//    }\n//}\n\n//static void CompileMeshPipeline(Hedgehog::Mirage::CMeshData* mesh, MeshLayer layer, CompilationArgs& args)\n//{\n//    CompileMeshPipeline(Mesh\n//        {\n//            mesh->m_VertexSize,\n//            0,\n//            reinterpret_cast<GuestVertexDeclaration*>(mesh->m_VertexDeclarationPtr.m_pD3DVertexDeclaration.get()),\n//            mesh->m_spMaterial.get(),\n//            layer,\n//            false\n//        }, args);\n//}\n\n//static void CompileMeshPipeline(Hedgehog::Mirage::CMorphModelData* morphModel, Hedgehog::Mirage::CMeshIndexData* mesh, MeshLayer layer, CompilationArgs& args)\n//{\n//    CompileMeshPipeline(Mesh\n//        {\n//            morphModel->m_VertexSize,\n//            morphModel->m_MorphTargetVertexSize,\n//            reinterpret_cast<GuestVertexDeclaration*>(morphModel->m_VertexDeclarationPtr.m_pD3DVertexDeclaration.get()),\n//            mesh->m_spMaterial.get(),\n//            layer,\n//            true\n//        }, args);\n//}\n\n//template<typename T>\n//static void CompileMeshPipelines(const T& modelData, CompilationArgs& args)\n//{\n//    for (auto& meshGroup : modelData.m_NodeGroupModels)\n//    {\n//        for (auto& mesh : meshGroup->m_OpaqueMeshes)\n//        {\n//            CompileMeshPipeline(mesh.get(), MeshLayer::Opaque, args);\n//\n//            if (args.noGI) // For models that can be shown transparent (eg. medals)\n//                CompileMeshPipeline(mesh.get(), MeshLayer::Transparent, args);\n//        }\n//\n//        for (auto& mesh : meshGroup->m_TransparentMeshes)\n//            CompileMeshPipeline(mesh.get(), MeshLayer::Transparent, args);\n//\n//        for (auto& mesh : meshGroup->m_PunchThroughMeshes)\n//            CompileMeshPipeline(mesh.get(), MeshLayer::PunchThrough, args);\n//\n//        for (auto& specialMeshGroup : meshGroup->m_SpecialMeshGroups)\n//        {\n//            for (auto& mesh : specialMeshGroup)\n//                CompileMeshPipeline(mesh.get(), MeshLayer::Special, args); // TODO: Are there layer types other than water in this game??\n//        }\n//    }\n//\n//    for (auto& mesh : modelData.m_OpaqueMeshes)\n//    {\n//        CompileMeshPipeline(mesh.get(), MeshLayer::Opaque, args);\n//\n//        if (args.noGI)\n//            CompileMeshPipeline(mesh.get(), MeshLayer::Transparent, args);\n//    }\n//\n//    for (auto& mesh : modelData.m_TransparentMeshes)\n//        CompileMeshPipeline(mesh.get(), MeshLayer::Transparent, args);\n//\n//    for (auto& mesh : modelData.m_PunchThroughMeshes)\n//        CompileMeshPipeline(mesh.get(), MeshLayer::PunchThrough, args);\n//\n//    if constexpr (std::is_same_v<T, Hedgehog::Mirage::CModelData>)\n//    {\n//        for (auto& morphModel : modelData.m_MorphModels)\n//        {\n//            for (auto& mesh : morphModel->m_OpaqueMeshList)\n//                CompileMeshPipeline(morphModel.get(), mesh.get(), MeshLayer::Opaque, args);\n//\n//            for (auto& mesh : morphModel->m_TransparentMeshList)\n//                CompileMeshPipeline(morphModel.get(), mesh.get(), MeshLayer::Transparent, args);\n//\n//            for (auto& mesh : morphModel->m_PunchThroughMeshList)\n//                CompileMeshPipeline(morphModel.get(), mesh.get(), MeshLayer::PunchThrough, args);\n//        }\n//    }\n//}\n\n//static void CompileParticleMaterialPipeline(const Hedgehog::Sparkle::CParticleMaterial& material, PipelineTaskTokenPair& tokenPair)\n//{\n//    auto& shaderList = material.m_spShaderListData;\n//    if (shaderList.get() == nullptr)\n//        return;\n//\n//    guest_stack_var<Hedgehog::Base::CStringSymbol> defaultSymbol(reinterpret_cast<const char*>(g_memory.Translate(0x8202DDBC)));\n//    auto defaultFindResult = shaderList->m_PixelShaderPermutations.find(*defaultSymbol);\n//    if (defaultFindResult == shaderList->m_PixelShaderPermutations.end())\n//        return;\n//\n//    guest_stack_var<Hedgehog::Base::CStringSymbol> noneSymbol(reinterpret_cast<const char*>(g_memory.Translate(0x8200D938)));\n//    auto noneFindResult = defaultFindResult->second.m_VertexShaderPermutations.find(*noneSymbol);\n//    if (noneFindResult == defaultFindResult->second.m_VertexShaderPermutations.end())\n//        return;\n//\n//    // All the particle models in the game come with the unoptimized format, so we can assume it.\n//    uint8_t unoptimizedVertexElements[144] =\n//    {\n//        0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x23, 0xB9, 0x00, 0x00, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x0C, 0x00, 0x2A, 0x23, 0xB9, 0x00, 0x03, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x18, 0x00, 0x2A, 0x23, 0xB9, 0x00, 0x06, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x24, 0x00, 0x2A, 0x23, 0xB9, 0x00, 0x07, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x30, 0x00, 0x2C, 0x23, 0xA5, 0x00, 0x05, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x38, 0x00, 0x2C, 0x23, 0xA5, 0x00, 0x05, 0x01, 0x00,\n//        0x00, 0x00, 0x00, 0x40, 0x00, 0x2C, 0x23, 0xA5, 0x00, 0x05, 0x02, 0x00,\n//        0x00, 0x00, 0x00, 0x48, 0x00, 0x2C, 0x23, 0xA5, 0x00, 0x05, 0x03, 0x00,\n//        0x00, 0x00, 0x00, 0x50, 0x00, 0x1A, 0x23, 0xA6, 0x00, 0x0A, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x60, 0x00, 0x1A, 0x23, 0x86, 0x00, 0x02, 0x00, 0x00,\n//        0x00, 0x00, 0x00, 0x64, 0x00, 0x1A, 0x20, 0x86, 0x00, 0x01, 0x00, 0x00,\n//        0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00\n//    };\n//\n//    auto unoptimizedVertexDeclaration = CreateVertexDeclarationWithoutAddRef(reinterpret_cast<GuestVertexElement*>(unoptimizedVertexElements));\n//    auto sparkleVertexDeclaration = CreateVertexDeclarationWithoutAddRef(reinterpret_cast<GuestVertexElement*>(g_memory.Translate(0x8211F540)));\n//\n//    bool isMeshShader = strstr(shaderList->m_TypeAndName.c_str(), \"Mesh\") != nullptr;\n//\n//    PipelineState pipelineState{};\n//    pipelineState.vertexShader = reinterpret_cast<GuestShader*>(noneFindResult->second->m_VertexShaders.begin()->second->m_spCode->m_pD3DVertexShader.get());\n//    pipelineState.pixelShader = reinterpret_cast<GuestShader*>(defaultFindResult->second.m_PixelShaders.begin()->second->m_spCode->m_pD3DPixelShader.get());\n//    pipelineState.vertexDeclaration = isMeshShader ? unoptimizedVertexDeclaration : sparkleVertexDeclaration;\n//    pipelineState.zWriteEnable = false;\n//    pipelineState.zFunc = RenderComparisonFunction::GREATER_EQUAL;\n//    pipelineState.alphaBlendEnable = true;\n//    pipelineState.srcBlendAlpha = RenderBlend::SRC_ALPHA;\n//    pipelineState.destBlendAlpha = RenderBlend::INV_SRC_ALPHA;\n//    pipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_STRIP;\n//    pipelineState.vertexStrides[0] = isMeshShader ? 104 : 28;\n//    pipelineState.depthStencilFormat = RenderFormat::D32_FLOAT_S8_UINT;\n//    pipelineState.specConstants = SPEC_CONSTANT_REVERSE_Z;\n//\n//    if (pipelineState.vertexDeclaration->hasR11G11B10Normal)\n//        pipelineState.specConstants |= SPEC_CONSTANT_R11G11B10_NORMAL;\n//\n//    switch (material.m_BlendMode.get())\n//    {\n//    case Hedgehog::Sparkle::CParticleMaterial::eBlendMode_Zero:\n//        pipelineState.srcBlend = RenderBlend::ZERO;\n//        pipelineState.destBlend = RenderBlend::ZERO;\n//        break;\n//    case Hedgehog::Sparkle::CParticleMaterial::eBlendMode_Typical:\n//        pipelineState.srcBlend = RenderBlend::SRC_ALPHA;\n//        pipelineState.destBlend = RenderBlend::INV_SRC_ALPHA;\n//        break;\n//    case Hedgehog::Sparkle::CParticleMaterial::eBlendMode_Add:\n//        pipelineState.srcBlend = RenderBlend::SRC_ALPHA;\n//        pipelineState.destBlend = RenderBlend::ONE;\n//        break;\n//    default:\n//        pipelineState.srcBlend = RenderBlend::ONE;\n//        pipelineState.destBlend = RenderBlend::ONE;\n//        break;\n//    }\n//\n//    auto createGraphicsPipeline = [&](PipelineState& pipelineStateToCreate)\n//        {\n//            SanitizePipelineState(pipelineStateToCreate);\n//            EnqueueGraphicsPipelineCompilation(pipelineStateToCreate, tokenPair, shaderList->m_TypeAndName.c_str() + 3);\n//        };\n//\n//    // Mesh particles can use both cull modes. Quad particles are only NONE.\n//    RenderCullMode cullModes[] = { RenderCullMode::NONE, RenderCullMode::BACK };\n//    uint32_t cullModeCount = isMeshShader ? std::size(cullModes) : 1;\n//    RenderFormat renderTargetFormats[] = { RenderFormat::R16G16B16A16_FLOAT, RenderFormat::R8G8B8A8_UNORM };\n//\n//    for (size_t i = 0; i < cullModeCount; i++)\n//    {\n//        pipelineState.cullMode = cullModes[i];\n//\n//        for (auto renderTargetFormat : renderTargetFormats)\n//        {\n//            pipelineState.renderTargetFormat = renderTargetFormat;\n//\n//            if (renderTargetFormat == RenderFormat::R16G16B16A16_FLOAT)\n//                pipelineState.sampleCount = Config::AntiAliasing != EAntiAliasing::None ? int32_t(Config::AntiAliasing.Value) : 1;\n//            else\n//                pipelineState.sampleCount = 1;\n//\n//            createGraphicsPipeline(pipelineState);\n//\n//            // Always compile no MSAA variant for particles, as the planar\n//            // reflection variable isn't reliable at this time of compilation.\n//            bool compileNoMsaaPipeline = pipelineState.sampleCount != 1;\n//\n//            auto noMsaaPipelineState = pipelineState;\n//            noMsaaPipelineState.sampleCount = 1;\n//\n//            if (compileNoMsaaPipeline)\n//                createGraphicsPipeline(noMsaaPipelineState);\n//\n//            if (!isMeshShader)\n//            {\n//                // Previous compilation was for locus particles. This one will be for quads.\n//                auto quadPipelineState = pipelineState;\n//                quadPipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST;\n//                createGraphicsPipeline(quadPipelineState);\n//\n//                if (compileNoMsaaPipeline)\n//                {\n//                    auto noMsaaQuadPipelineState = noMsaaPipelineState;\n//                    noMsaaQuadPipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST;\n//                    createGraphicsPipeline(noMsaaQuadPipelineState);\n//                }\n//            }\n//        }\n//    }\n//}\n\nstatic std::thread::id g_mainThreadId = std::this_thread::get_id();\n\n// SWA::CGameModeStage::ExitLoading\n// PPC_FUNC_IMPL(__imp__sub_825369A0);\n// PPC_FUNC(sub_825369A0)\n// {\n//     assert(std::this_thread::get_id() == g_mainThreadId);\n//\n//     // Wait for pipeline compilations to finish.\n//     uint32_t value;\n//     while ((value = g_compilingPipelineTaskCount.load()) != 0)\n//     {\n//         // Pump SDL events to prevent the OS\n//         // from thinking the process is unresponsive.\n//         SDL_PumpEvents();\n//         SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);\n//\n//         g_compilingPipelineTaskCount.wait(value);\n//     }\n//\n//     __imp__sub_825369A0(ctx, base);\n// }\n\n// CModelData::CheckMadeAll\n// PPC_FUNC_IMPL(__imp__sub_82E2EFB0);\n// PPC_FUNC(sub_82E2EFB0)\n// {   \n//     if (reinterpret_cast<Hedgehog::Database::CDatabaseData*>(base + ctx.r3.u32)->m_Flags & eDatabaseDataFlags_CompilingPipelines)\n//     {\n//         ctx.r3.u64 = 0;\n//     }\n//     else\n//     {\n//         __imp__sub_82E2EFB0(ctx, base);\n//     }\n// }\n\n// CTerrainModelData::CheckMadeAll\n// PPC_FUNC_IMPL(__imp__sub_82E243D8);\n// PPC_FUNC(sub_82E243D8)\n// {   \n//     if (reinterpret_cast<Hedgehog::Database::CDatabaseData*>(base + ctx.r3.u32)->m_Flags & eDatabaseDataFlags_CompilingPipelines)\n//     {\n//         ctx.r3.u64 = 0;\n//     }\n//     else\n//     {\n//         __imp__sub_82E243D8(ctx, base);\n//     }\n// }\n\n// CParticleMaterial::CheckMadeAll\n// PPC_FUNC_IMPL(__imp__sub_82E87598);\n// PPC_FUNC(sub_82E87598)\n// {   \n//     if (reinterpret_cast<Hedgehog::Database::CDatabaseData*>(base + ctx.r3.u32)->m_Flags & eDatabaseDataFlags_CompilingPipelines)\n//     {\n//         ctx.r3.u64 = 0;\n//     }\n//     else\n//     {\n//         __imp__sub_82E87598(ctx, base);\n//     }\n// }\n\n//void GetDatabaseDataMidAsmHook(PPCRegister& r1, PPCRegister& r4)\n//{\n//    auto& databaseData = *reinterpret_cast<boost::shared_ptr<Hedgehog::Database::CDatabaseData>*>(\n//        g_memory.Translate(r1.u32 + 0x58));\n//\n//    if (!databaseData->IsMadeOne() && r4.u32 != NULL)\n//    {\n//        if (databaseData->m_pVftable.ptr == MODEL_DATA_VFTABLE)\n//        {\n//            // Ignore particle models, the materials they point at don't actually\n//            // get used and give the threads unnecessary work.\n//            bool isParticleModel = *reinterpret_cast<be<uint32_t>*>(g_memory.Translate(r4.u32 + 4)) != 5 &&\n//                strncmp(databaseData->m_TypeAndName.c_str() + 2, \"eff_\", 4) == 0;\n//\n//            if (isParticleModel)\n//                return;\n//\n//            // Adabat water is broken in original game, which they tried to fix by partially including the files in the update,\n//            // which then finally fixed for real in the DLC. This confuses the async PSO compiler and causes a hang if the DLC is missing.\n//            // We'll just ignore it.\n//            bool isAdabatWater = strcmp(databaseData->m_TypeAndName.c_str() + 2, \"evl_sea_obj_st_waterCircle\") == 0;\n//            if (isAdabatWater)\n//                return;\n//        }\n//\n//        databaseData->m_Flags |= eDatabaseDataFlags_CompilingPipelines;\n//        EnqueuePipelineTask(PipelineTaskType::DatabaseData, databaseData);\n//    }\n//}\n\n//static bool CheckMadeAll(Hedgehog::Mirage::CMeshData* meshData)\n//{\n//    if (!meshData->IsMadeOne())\n//        return false;\n//\n//    if (meshData->m_spMaterial.get() != nullptr)\n//    {\n//        if (!meshData->m_spMaterial->IsMadeOne())\n//            return false;\n//\n//        if (meshData->m_spMaterial->m_spTexsetData.get() != nullptr)\n//        {\n//            if (!meshData->m_spMaterial->m_spTexsetData->IsMadeOne())\n//                return false;\n//\n//            for (auto& texture : meshData->m_spMaterial->m_spTexsetData->m_TextureList)\n//            {\n//                if (!texture->IsMadeOne())\n//                    return false;\n//            }\n//        }\n//    }\n//\n//    return true;\n//}\n\ntemplate<typename T>\nstatic bool CheckMadeAll(const T& modelData)\n{\n    if (!modelData.IsMadeOne())\n        return false;\n\n    for (auto& meshGroup : modelData.m_NodeGroupModels)\n    {\n        for (auto& mesh : meshGroup->m_OpaqueMeshes)\n        {\n            if (!CheckMadeAll(mesh.get()))\n                return false;\n        }     \n\n        for (auto& mesh : meshGroup->m_TransparentMeshes)\n        {\n            if (!CheckMadeAll(mesh.get()))\n                return false;\n        }    \n\n        for (auto& mesh : meshGroup->m_PunchThroughMeshes)\n        {\n            if (!CheckMadeAll(mesh.get()))\n                return false;\n        }\n\n        for (auto& specialMeshGroup : meshGroup->m_SpecialMeshGroups)\n        {\n            for (auto& mesh : specialMeshGroup)\n            {\n                if (!CheckMadeAll(mesh.get()))\n                    return false;\n            }\n        }\n    }\n\n    for (auto& mesh : modelData.m_OpaqueMeshes)\n    {\n        if (!CheckMadeAll(mesh.get()))\n            return false;\n    }\n\n    for (auto& mesh : modelData.m_TransparentMeshes)\n    {\n        if (!CheckMadeAll(mesh.get()))\n            return false;\n    }\n\n    for (auto& mesh : modelData.m_PunchThroughMeshes)\n    {\n        if (!CheckMadeAll(mesh.get()))\n            return false;\n    }\n\n    return true;\n}\n\n//static void PipelineTaskConsumerThread()\n//{\n//#ifdef _WIN32\n//    SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);\n//    GuestThread::SetThreadName(GetCurrentThreadId(), \"Pipeline Task Consumer Thread\");\n//#endif\n//\n//    std::vector<PipelineTask> localPipelineTaskQueue;\n//    std::unique_ptr<GuestThreadContext> ctx;\n//\n//    while (true)\n//    {\n//        // Wait for tasks to arrive.\n//        uint32_t pendingPipelineTaskCount;\n//        while ((pendingPipelineTaskCount = g_pendingPipelineTaskCount.load()) == 0)\n//            g_pendingPipelineTaskCount.wait(pendingPipelineTaskCount);\n//\n//        if (ctx == nullptr)\n//            ctx = std::make_unique<GuestThreadContext>(0);\n//\n//        {\n//            std::lock_guard lock(g_pipelineTaskMutex);\n//            localPipelineTaskQueue.insert(localPipelineTaskQueue.end(), g_pipelineTaskQueue.begin(), g_pipelineTaskQueue.end());\n//            g_pipelineTaskQueue.clear();\n//        }\n//\n//        bool allHandled = true;\n//\n//        for (auto& [type, databaseData] : localPipelineTaskQueue)\n//        {\n//            switch (type)\n//            {\n//            case PipelineTaskType::DatabaseData:\n//            {\n//                bool ready = false;\n//\n//                if (databaseData->m_pVftable.ptr == MODEL_DATA_VFTABLE)\n//                    ready = CheckMadeAll(*reinterpret_cast<Hedgehog::Mirage::CModelData*>(databaseData.get()));\n//                else\n//                    ready = databaseData->IsMadeOne();\n//\n//                if (ready || databaseData.unique())\n//                {\n//                    if (databaseData->m_pVftable.ptr == TERRAIN_MODEL_DATA_VFTABLE)\n//                    {\n//                        CompilationArgs args{};\n//                        args.tokenPair.token.type = type;\n//                        args.tokenPair.token.databaseData = databaseData;\n//                        args.instancing = strncmp(databaseData->m_TypeAndName.c_str() + 3, \"ins\", 3) == 0;\n//                        CompileMeshPipelines(*reinterpret_cast<Hedgehog::Mirage::CTerrainModelData*>(databaseData.get()), args);\n//                    }\n//                    else if (databaseData->m_pVftable.ptr == PARTICLE_MATERIAL_VFTABLE)\n//                    {\n//                        PipelineTaskTokenPair tokenPair;\n//                        tokenPair.token.type = type;\n//                        tokenPair.token.databaseData = databaseData;\n//                        CompileParticleMaterialPipeline(*reinterpret_cast<Hedgehog::Sparkle::CParticleMaterial*>(databaseData.get()), tokenPair);\n//                    }\n//                    else\n//                    {\n//                        assert(databaseData->m_pVftable.ptr == MODEL_DATA_VFTABLE);\n//\n//                        auto modelData = reinterpret_cast<Hedgehog::Mirage::CModelData*>(databaseData.get());\n//\n//                        CompilationArgs args{};\n//                        args.tokenPair.token.type = type;\n//                        args.tokenPair.token.databaseData = databaseData;\n//                        args.noGI = true;\n//                        args.hasMoreThanOneBone = modelData->m_NodeNum > 1;\n//                        args.velocityMapQuickStep = strcmp(databaseData->m_TypeAndName.c_str() + 2, \"SonicRoot\") == 0;\n//\n//                        // Check for the on screen items, eg. rings going to HUD.\n//                        auto items = reinterpret_cast<xpointer<const char>*>(g_memory.Translate(0x832A8DD0));\n//                        for (size_t i = 0; i < 50; i++)\n//                        {\n//                            if (strcmp(databaseData->m_TypeAndName.c_str() + 2, (*items).get()) == 0)\n//                            {\n//                                args.objectIcon = true;\n//                                break;\n//                            }\n//                            items += 7;\n//                        }\n//\n//                        CompileMeshPipelines(*modelData, args);\n//                    }\n//\n//                    type = PipelineTaskType::Null;\n//                    databaseData = nullptr;\n//\n//                    --g_pendingPipelineTaskCount;\n//                }\n//                else\n//                {\n//                    allHandled = false;\n//                }\n//\n//                break;\n//            }\n//\n//            case PipelineTaskType::PrecompilePipelines:\n//            {\n//                // Deliberately leaving the type null to account for the enqueue\n//                // call not incrementing the compiling pipeline task counter.\n//                PipelineTaskTokenPair tokenPair;\n//\n//                for (auto vertexElements : g_vertexDeclarationCache)\n//                    CreateVertexDeclarationWithoutAddRef(reinterpret_cast<GuestVertexElement*>(vertexElements));\n//\n//                for (auto pipelineState : g_pipelineStateCache)\n//                {\n//                    // The hashes were reinterpret casted to pointers in the cache.\n//                    pipelineState.vertexShader = FindShaderCacheEntry(reinterpret_cast<XXH64_hash_t>(pipelineState.vertexShader))->guestShader;\n//\n//                    if (pipelineState.pixelShader != nullptr)\n//                        pipelineState.pixelShader = FindShaderCacheEntry(reinterpret_cast<XXH64_hash_t>(pipelineState.pixelShader))->guestShader;\n//\n//                    {\n//                        std::lock_guard lock(g_vertexDeclarationMutex);\n//                        pipelineState.vertexDeclaration = g_vertexDeclarations[reinterpret_cast<XXH64_hash_t>(pipelineState.vertexDeclaration)];\n//                    }\n//\n//                    if (!g_capabilities.triangleFan && pipelineState.primitiveTopology == RenderPrimitiveTopology::TRIANGLE_FAN)\n//                        pipelineState.primitiveTopology = RenderPrimitiveTopology::TRIANGLE_LIST;\n//\n//                    // Zero out depth bias for Vulkan, we only store common values for D3D12.\n//                    if (g_capabilities.dynamicDepthBias && g_backend != Backend::D3D12)\n//                    {\n//                        pipelineState.depthBias = 0;\n//                        pipelineState.slopeScaledDepthBias = 0.0f;\n//                    }\n//\n//                    if (Config::GITextureFiltering == EGITextureFiltering::Bicubic)\n//                        pipelineState.specConstants |= SPEC_CONSTANT_BICUBIC_GI_FILTER;\n//\n//                    auto createGraphicsPipeline = [&](PipelineState& pipelineStateToCreate, const char* name)\n//                        {\n//                            SanitizePipelineState(pipelineStateToCreate);\n//                            EnqueueGraphicsPipelineCompilation(pipelineStateToCreate, tokenPair, name, true);\n//                        };\n//\n//                    // Compile both MSAA and non MSAA variants to work with reflection maps. The render formats are an assumption but it should hold true.\n//                    if (Config::AntiAliasing != EAntiAliasing::None &&\n//                        pipelineState.renderTargetFormat == RenderFormat::R16G16B16A16_FLOAT &&\n//                        pipelineState.depthStencilFormat == RenderFormat::D32_FLOAT_S8_UINT)\n//                    {\n//                        auto msaaPipelineState = pipelineState;\n//                        msaaPipelineState.sampleCount = int32_t(Config::AntiAliasing.Value);\n//\n//                        if (Config::TransparencyAntiAliasing && (msaaPipelineState.specConstants & SPEC_CONSTANT_ALPHA_TEST) != 0)\n//                        {\n//                            msaaPipelineState.enableAlphaToCoverage = true;\n//                            msaaPipelineState.specConstants &= ~SPEC_CONSTANT_ALPHA_TEST;\n//                            msaaPipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TO_COVERAGE;\n//                        }\n//\n//                        createGraphicsPipeline(msaaPipelineState, \"Precompiled Pipeline MSAA\");\n//                    }\n//\n//                    if (pipelineState.pixelShader != nullptr &&\n//                        pipelineState.pixelShader->shaderCacheEntry != nullptr)\n//                    {\n//                        XXH64_hash_t hash = pipelineState.pixelShader->shaderCacheEntry->hash;\n//\n//                        // Compile the custom gaussian blur shaders that we pass to the game.\n//                        if (hash == 0x4294510C775F4EE8)\n//                        {\n//                            for (auto& shader : g_gaussianBlurShaders)\n//                            {\n//                                auto newPipelineState = pipelineState;\n//                                newPipelineState.pixelShader = shader.get();\n//                                createGraphicsPipeline(newPipelineState, \"Precompiled Gaussian Blur Pipeline\");\n//                            }\n//                        }\n//                        // Compile enhanced motion blur shader.\n//                        else if (hash == 0x6B9732B4CD7E7740)\n//                        {\n//                            auto newPipelineState = pipelineState;\n//                            newPipelineState.pixelShader = g_enhancedMotionBlurShader.get();\n//                            createGraphicsPipeline(newPipelineState, \"Precompiled Enhanced Motion Blur Pipeline\");\n//                        }\n//                    }\n//\n//                    createGraphicsPipeline(pipelineState, \"Precompiled Pipeline\");\n//\n//                    // Compile the CSD filter shader that we pass to the game when point filtering is used.\n//                    if (pipelineState.pixelShader == g_csdShader)\n//                    {\n//                        pipelineState.pixelShader = g_csdFilterShader.get();\n//                        createGraphicsPipeline(pipelineState, \"Precompiled CSD Filter Pipeline\");\n//                    }\n//                }\n//\n//                type = PipelineTaskType::Null;\n//                --g_pendingPipelineTaskCount;\n//\n//                break;\n//            }\n//\n//            case PipelineTaskType::RecompilePipelines:\n//            {\n//                PipelineTaskTokenPair tokenPair;\n//                tokenPair.token.type = type;\n//\n//                auto asyncPipelines = g_asyncPipelineStates.values();\n//\n//                for (auto& [hash, pipelineState] : asyncPipelines)\n//                {\n//                    bool alphaTest = (pipelineState.specConstants & (SPEC_CONSTANT_ALPHA_TEST | SPEC_CONSTANT_ALPHA_TO_COVERAGE)) != 0;\n//                    bool msaa = pipelineState.sampleCount != 1 || (pipelineState.renderTargetFormat == RenderFormat::R16G16B16A16_FLOAT && pipelineState.depthStencilFormat == RenderFormat::D32_FLOAT_S8_UINT);\n//\n//                    pipelineState.sampleCount = 1;\n//                    pipelineState.enableAlphaToCoverage = false;\n//                    pipelineState.specConstants &= ~(SPEC_CONSTANT_BICUBIC_GI_FILTER | SPEC_CONSTANT_ALPHA_TEST | SPEC_CONSTANT_ALPHA_TO_COVERAGE);\n//\n//                    if (msaa && Config::AntiAliasing != EAntiAliasing::None)\n//                    {\n//                        pipelineState.sampleCount = int32_t(Config::AntiAliasing.Value);\n//\n//                        if (alphaTest)\n//                        {\n//                            if (Config::TransparencyAntiAliasing)\n//                            {\n//                                pipelineState.enableAlphaToCoverage = true;\n//                                pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TO_COVERAGE;\n//                            }\n//                            else\n//                            {\n//                                pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TEST;\n//                            }\n//                        }\n//                    }\n//                    else if (alphaTest)\n//                    {\n//                        pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TEST;\n//                    }\n//\n//                    if (Config::GITextureFiltering == EGITextureFiltering::Bicubic)\n//                        pipelineState.specConstants |= SPEC_CONSTANT_BICUBIC_GI_FILTER;\n//\n//                    SanitizePipelineState(pipelineState);\n//                    EnqueueGraphicsPipelineCompilation(pipelineState, tokenPair, \"Recompiled Pipeline State\");\n//                }\n//\n//                type = PipelineTaskType::Null;\n//                --g_pendingPipelineTaskCount;\n//\n//                break;\n//            }\n//            }\n//        }\n//\n//        if (allHandled)\n//            localPipelineTaskQueue.clear();\n//\n//        std::this_thread::yield();\n//    }\n//}\n\n//static std::thread g_pipelineTaskConsumerThread(PipelineTaskConsumerThread);\n\n#ifdef ASYNC_PSO_DEBUG\n\n// PPC_FUNC_IMPL(__imp__sub_82E33330);\n// PPC_FUNC(sub_82E33330)\n// {\n//     auto vertexShaderCode = reinterpret_cast<Hedgehog::Mirage::CVertexShaderCodeData*>(g_memory.Translate(ctx.r4.u32));\n//     __imp__sub_82E33330(ctx, base);\n//     reinterpret_cast<GuestShader*>(vertexShaderCode->m_pD3DVertexShader.get())->name = vertexShaderCode->m_TypeAndName.c_str() + 3;\n// }\n\n// PPC_FUNC_IMPL(__imp__sub_82E328D8);\n// PPC_FUNC(sub_82E328D8)\n// {\n//     auto pixelShaderCode = reinterpret_cast<Hedgehog::Mirage::CPixelShaderCodeData*>(g_memory.Translate(ctx.r4.u32));\n//     __imp__sub_82E328D8(ctx, base);\n//     reinterpret_cast<GuestShader*>(pixelShaderCode->m_pD3DPixelShader.get())->name = pixelShaderCode->m_TypeAndName.c_str() + 2;\n// }\n\n#endif\n\n#ifdef PSO_CACHING\nclass SDLEventListenerForPSOCaching : public SDLEventListener\n{\npublic:\n    bool OnSDLEvent(SDL_Event* event) override \n    {\n        if (event->type != SDL_QUIT)\n            return false;\n\n        std::lock_guard lock(g_pipelineCacheMutex);\n        if (g_pipelineStatesToCache.empty())\n            return false;\n\n        FILE* f = fopen(\"send_this_file_to_skyth.txt\", \"ab\");\n        if (f != nullptr)\n        {\n            ankerl::unordered_dense::set<GuestVertexDeclaration*> vertexDeclarations;\n            xxHashMap<PipelineState> pipelineStatesToCache;\n\n            for (auto& [hash, pipelineState] : g_pipelineStatesToCache)\n            {\n                if (pipelineState.vertexShader->shaderCacheEntry == nullptr ||\n                    (pipelineState.pixelShader != nullptr && pipelineState.pixelShader->shaderCacheEntry == nullptr))\n                {\n                    continue;\n                }\n\n                vertexDeclarations.emplace(pipelineState.vertexDeclaration);\n\n                // Mask out the config options.\n                pipelineState.sampleCount = 1;\n                pipelineState.enableAlphaToCoverage = false;\n\n                if ((pipelineState.specConstants & SPEC_CONSTANT_ALPHA_TO_COVERAGE) != 0)\n                {\n                    pipelineState.specConstants &= ~SPEC_CONSTANT_ALPHA_TO_COVERAGE;\n                    pipelineState.specConstants |= SPEC_CONSTANT_ALPHA_TEST;\n                }\n\n                pipelineStatesToCache.emplace(XXH3_64bits(&pipelineState, sizeof(pipelineState)), pipelineState);\n            }\n\n            for (auto vertexDeclaration : vertexDeclarations)\n            {\n                fmt::print(f, \"static uint8_t g_vertexElements_{:016X}[] = {{\", vertexDeclaration->hash);\n\n                auto bytes = reinterpret_cast<uint8_t*>(vertexDeclaration->vertexElements.get());\n                for (size_t i = 0; i < vertexDeclaration->vertexElementCount * sizeof(GuestVertexElement); i++)\n                    fmt::print(f, \"0x{:X},\", bytes[i]);\n\n                fmt::println(f, \"}};\");\n            }\n\n            for (auto& [pipelineHash, pipelineState] : pipelineStatesToCache)\n            {\n                fmt::println(f, \"{{ \"\n                    \"reinterpret_cast<GuestShader*>(0x{:X}),\"\n                    \"reinterpret_cast<GuestShader*>(0x{:X}),\"\n                    \"reinterpret_cast<GuestVertexDeclaration*>(0x{:X}),\"\n                    \"{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"RenderBlend::{},\"\n                    \"RenderBlend::{},\"\n                    \"RenderCullMode::{},\"\n                    \"RenderFrontFace::{},\"\n                    \"RenderComparisonFunction::{},\"\n                    \"RenderComparisonFunction::{},\"\n                    \"RenderStencilOp::{},\"\n                    \"RenderStencilOp::{},\"\n                    \"RenderStencilOp::{},\"\n                    \"RenderComparisonFunction::{},\"\n                    \"RenderStencilOp::{},\"\n                    \"RenderStencilOp::{},\"\n                    \"RenderStencilOp::{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"RenderBlendOperation::{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"RenderBlend::{},\"\n                    \"RenderBlend::{},\"\n                    \"RenderBlendOperation::{},\"\n                    \"0x{:X},\"\n                    \"RenderPrimitiveTopology::{},\"\n                    \"{{ {},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{} }},\"\n                    \"RenderFormat::{},\"\n                    \"RenderFormat::{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"{},\"\n                    \"0x{:X} }},\",\n                    pipelineState.vertexShader->shaderCacheEntry->hash,\n                    pipelineState.pixelShader != nullptr ? pipelineState.pixelShader->shaderCacheEntry->hash : 0,\n                    pipelineState.vertexDeclaration->hash,\n                    pipelineState.zEnable,\n                    pipelineState.zWriteEnable,\n                    pipelineState.stencilEnable,\n                    pipelineState.stencilTwoSided,\n                    magic_enum::enum_name(pipelineState.srcBlend),\n                    magic_enum::enum_name(pipelineState.destBlend),\n                    magic_enum::enum_name(pipelineState.cullMode),\n                    magic_enum::enum_name(pipelineState.frontFace),\n                    magic_enum::enum_name(pipelineState.zFunc),\n                    magic_enum::enum_name(pipelineState.stencilFunc),\n                    magic_enum::enum_name(pipelineState.stencilFail),\n                    magic_enum::enum_name(pipelineState.stencilZFail),\n                    magic_enum::enum_name(pipelineState.stencilPass),\n                    magic_enum::enum_name(pipelineState.stencilFuncCCW),\n                    magic_enum::enum_name(pipelineState.stencilFailCCW),\n                    magic_enum::enum_name(pipelineState.stencilZFailCCW),\n                    magic_enum::enum_name(pipelineState.stencilPassCCW),\n                    pipelineState.stencilMask,\n                    pipelineState.stencilWriteMask,\n                    pipelineState.stencilRef,\n                    pipelineState.alphaBlendEnable,\n                    magic_enum::enum_name(pipelineState.blendOp),\n                    pipelineState.slopeScaledDepthBias,\n                    pipelineState.depthBias,\n                    magic_enum::enum_name(pipelineState.srcBlendAlpha),\n                    magic_enum::enum_name(pipelineState.destBlendAlpha),\n                    magic_enum::enum_name(pipelineState.blendOpAlpha),\n                    pipelineState.colorWriteEnable,\n                    magic_enum::enum_name(pipelineState.primitiveTopology),\n                    pipelineState.vertexStrides[0],\n                    pipelineState.vertexStrides[1],\n                    pipelineState.vertexStrides[2],\n                    pipelineState.vertexStrides[3],\n                    pipelineState.vertexStrides[4],\n                    pipelineState.vertexStrides[5],\n                    pipelineState.vertexStrides[6],\n                    pipelineState.vertexStrides[7],\n                    pipelineState.vertexStrides[8],\n                    pipelineState.vertexStrides[9],\n                    pipelineState.vertexStrides[10],\n                    pipelineState.vertexStrides[11],\n                    pipelineState.vertexStrides[12],\n                    pipelineState.vertexStrides[13],\n                    pipelineState.vertexStrides[14],\n                    pipelineState.vertexStrides[15],\n                    magic_enum::enum_name(pipelineState.renderTargetFormat),\n                    magic_enum::enum_name(pipelineState.depthStencilFormat),\n                    pipelineState.sampleCount,\n                    pipelineState.enableAlphaToCoverage,\n                    pipelineState.enableConditionalSurvey,\n                    pipelineState.specConstants);\n            }\n\n            fclose(f);\n        }\n\n        return false;\n    }\n};\nSDLEventListenerForPSOCaching g_sdlEventListenerForPSOCaching;\n#endif\n\nvoid VideoConfigValueChangedCallback(IConfigDef* config)\n{\n    // Config options that require internal resolution resize\n    g_needsResize |=\n        config == &Config::AspectRatio ||\n        config == &Config::ResolutionScale ||\n        config == &Config::AntiAliasing ||\n        config == &Config::ShadowResolution;\n\n    if (g_needsResize)\n        Video::ComputeViewportDimensions();\n        \n    // Config options that require pipeline recompilation\n    bool shouldRecompile =\n        config == &Config::AntiAliasing ||\n        config == &Config::TransparencyAntiAliasing;\n\n//    if (shouldRecompile)\n//        EnqueuePipelineTask(PipelineTaskType::RecompilePipelines, {});\n}\n\n// There is a bug on AMD where restart indices cause incorrect culling and prevent some triangles from being rendered.\n// This seems to happen on both Windows AMD drivers and Mesa. Converting restart indices to degenerate triangles fixes it.\nstatic void ConvertToDegenerateTriangles(uint16_t* indices, uint32_t indexCount, uint16_t*& newIndices, uint32_t& newIndexCount)\n{\n    newIndices = reinterpret_cast<uint16_t*>(g_userHeap.Alloc(indexCount * sizeof(uint16_t) * 3));\n    newIndexCount = 0;\n\n    bool stripStart = true;\n    uint32_t stripSize = 0;\n    uint16_t lastIndex = 0;\n\n    for (uint32_t i = 0; i < indexCount; i++)\n    {\n        uint16_t index = indices[i];\n        if (index == 0xFFFF)\n        {\n            if ((stripSize % 2) != 0)\n                newIndices[newIndexCount++] = lastIndex;\n\n            stripStart = true;\n            stripSize = 0;\n        }\n        else \n        {\n            if (stripStart && newIndexCount != 0)\n            {\n                newIndices[newIndexCount++] = lastIndex;\n                newIndices[newIndexCount++] = index;\n            }\n\n            newIndices[newIndexCount++] = index;\n            stripStart = false;\n            ++stripSize;\n            lastIndex = index;\n        }\n    }\n}\n\nstruct MeshResource\n{\n    MARATHON_INSERT_PADDING(0x4);\n    be<uint32_t> indexCount;\n    be<uint32_t> indices;\n};\n\nstatic std::vector<uint16_t*> g_newIndicesToFree;\n\n// Hedgehog::Mirage::CMeshData::Make\n// PPC_FUNC_IMPL(__imp__sub_82E44AF8);\n// PPC_FUNC(sub_82E44AF8)\n// {\n//     uint16_t* newIndicesToFree = nullptr;\n\n//     auto databaseData = reinterpret_cast<Hedgehog::Database::CDatabaseData*>(base + ctx.r3.u32);\n//     if (g_triangleStripWorkaround && !databaseData->IsMadeOne())\n//     {\n//         auto meshResource = reinterpret_cast<MeshResource*>(base + ctx.r4.u32);\n\n//         if (meshResource->indexCount != 0)\n//         {\n//             uint16_t* newIndices;\n//             uint32_t newIndexCount;\n\n//             ConvertToDegenerateTriangles(\n//                 reinterpret_cast<uint16_t*>(base + meshResource->indices),\n//                 meshResource->indexCount,\n//                 newIndices,\n//                 newIndexCount);\n\n//             meshResource->indexCount = newIndexCount;\n//             meshResource->indices = static_cast<uint32_t>(reinterpret_cast<uint8_t*>(newIndices) - base);\n\n//             if (PPC_LOAD_U32(0x83396E98) != NULL)\n//             {\n//                 // If index buffers are getting merged, new indices need to survive until the merge happens.\n//                 g_newIndicesToFree.push_back(newIndices);\n//             }\n//             else \n//             {\n//                 // Otherwise, we can free it immediately.\n//                 newIndicesToFree = newIndices;\n//             }\n//         }\n//     }\n\n//     __imp__sub_82E44AF8(ctx, base);\n\n//     if (newIndicesToFree != nullptr)\n//         g_userHeap.Free(newIndicesToFree);\n// }\n\n// Hedgehog::Mirage::CShareVertexBuffer::Reset\n// PPC_FUNC_IMPL(__imp__sub_82E250D0);\n// PPC_FUNC(sub_82E250D0)\n// {\n//     __imp__sub_82E250D0(ctx, base);\n\n//     for (auto newIndicesToFree : g_newIndicesToFree)\n//         g_userHeap.Free(newIndicesToFree);\n\n//     g_newIndicesToFree.clear();\n// }\n\nstruct LightAndIndexBufferResourceV1\n{\n    MARATHON_INSERT_PADDING(0x4);\n    be<uint32_t> indexCount;\n    be<uint32_t> indices;\n};\n\n// Hedgehog::Mirage::CLightAndIndexBufferData::MakeV1\n// PPC_FUNC_IMPL(__imp__sub_82E3AFC8);\n// PPC_FUNC(sub_82E3AFC8)\n// {\n//     uint16_t* newIndices = nullptr;\n\n//     auto databaseData = reinterpret_cast<Hedgehog::Database::CDatabaseData*>(base + ctx.r3.u32);\n//     if (g_triangleStripWorkaround && !databaseData->IsMadeOne())\n//     {\n//         auto lightAndIndexBufferResource = reinterpret_cast<LightAndIndexBufferResourceV1*>(base + ctx.r4.u32);\n\n//         if (lightAndIndexBufferResource->indexCount != 0)\n//         {\n//             uint32_t newIndexCount;\n\n//             ConvertToDegenerateTriangles(\n//                 reinterpret_cast<uint16_t*>(base + lightAndIndexBufferResource->indices),\n//                 lightAndIndexBufferResource->indexCount,\n//                 newIndices,\n//                 newIndexCount);\n\n//             lightAndIndexBufferResource->indexCount = newIndexCount;\n//             lightAndIndexBufferResource->indices = static_cast<uint32_t>(reinterpret_cast<uint8_t*>(newIndices) - base);\n//         }\n//     }\n\n//     __imp__sub_82E3AFC8(ctx, base);\n\n//     if (newIndices != nullptr)\n//         g_userHeap.Free(newIndices);\n// }\n\nstruct LightAndIndexBufferResourceV5\n{\n    MARATHON_INSERT_PADDING(0x8);\n    be<uint32_t> indexCount;\n    be<uint32_t> indices;\n};\n\n// Hedgehog::Mirage::CLightAndIndexBufferData::MakeV5\n// PPC_FUNC_IMPL(__imp__sub_82E3B1C0);\n// PPC_FUNC(sub_82E3B1C0)\n// {\n//     uint16_t* newIndices = nullptr;\n\n//     auto databaseData = reinterpret_cast<Hedgehog::Database::CDatabaseData*>(base + ctx.r3.u32);\n//     if (g_triangleStripWorkaround && !databaseData->IsMadeOne())\n//     {\n//         auto lightAndIndexBufferResource = reinterpret_cast<LightAndIndexBufferResourceV5*>(base + ctx.r4.u32);\n\n//         if (lightAndIndexBufferResource->indexCount != 0)\n//         {\n//             uint32_t newIndexCount;\n\n//             ConvertToDegenerateTriangles(\n//                 reinterpret_cast<uint16_t*>(base + lightAndIndexBufferResource->indices),\n//                 lightAndIndexBufferResource->indexCount,\n//                 newIndices,\n//                 newIndexCount);\n\n//             lightAndIndexBufferResource->indexCount = newIndexCount;\n//             lightAndIndexBufferResource->indices = static_cast<uint32_t>(reinterpret_cast<uint8_t*>(newIndices) - base);\n//         }\n//     }\n\n//     __imp__sub_82E3B1C0(ctx, base);\n\n//     if (newIndices != nullptr)\n//         g_userHeap.Free(newIndices);\n// }\n\nGUEST_FUNCTION_HOOK(sub_8253EC98, CreateDevice);\n\nGUEST_FUNCTION_HOOK(sub_8253AE98, DestructResource);\n\nGUEST_FUNCTION_HOOK(sub_8253A740, LockTextureRect);\nGUEST_FUNCTION_HOOK(sub_82538D30, UnlockTextureRect);\n\nGUEST_FUNCTION_HOOK(sub_8253B5D0, LockVertexBuffer);\nGUEST_FUNCTION_HOOK(sub_8253B630, UnlockVertexBuffer);\n// GUEST_FUNCTION_HOOK(sub_82BE61D0, GetVertexBufferDesc);\n\nGUEST_FUNCTION_HOOK(sub_8253B6F0, LockIndexBuffer);\nGUEST_FUNCTION_HOOK(sub_8253B750, UnlockIndexBuffer);\n// GUEST_FUNCTION_HOOK(sub_82BE6200, GetIndexBufferDesc);\n\nGUEST_FUNCTION_HOOK(sub_8253AB20, GetSurfaceDesc);\n\nGUEST_FUNCTION_HOOK(sub_825471F8, GetVertexDeclaration);\n// GUEST_FUNCTION_HOOK(sub_82BE0530, HashVertexDeclaration);\n\nGUEST_FUNCTION_HOOK(sub_825586B0, Video::Present);\nGUEST_FUNCTION_HOOK(sub_82543B58, GetBackBuffer);\nGUEST_FUNCTION_HOOK(sub_82543BA0, GetDepthStencil);\n\nGUEST_FUNCTION_HOOK(sub_8253A8D8, CreateTexture);\nGUEST_FUNCTION_HOOK(sub_8253B508, CreateVertexBuffer);\nGUEST_FUNCTION_HOOK(sub_8253B640, CreateIndexBuffer);\nGUEST_FUNCTION_HOOK(sub_8253A9F8, CreateSurface);\n\nGUEST_FUNCTION_HOOK(sub_825575B8, StretchRect);\n\nGUEST_FUNCTION_HOOK(sub_82543EE0, SetRenderTarget);\nGUEST_FUNCTION_HOOK(sub_825444F0, SetRenderTarget);\nGUEST_FUNCTION_HOOK(sub_82544210, SetDepthStencilSurface);\n\nGUEST_FUNCTION_HOOK(sub_82555B30, Clear);\n\nGUEST_FUNCTION_HOOK(sub_825436F0, SetViewport);\n\nGUEST_FUNCTION_HOOK(sub_8253AC40, SetTexture);\nGUEST_FUNCTION_HOOK(sub_82543628, SetScissorRect);\n\nGUEST_FUNCTION_HOOK(sub_826FEC28, DrawPrimitive);\nGUEST_FUNCTION_HOOK(sub_826FF030, DrawIndexedPrimitive);\nGUEST_FUNCTION_HOOK(sub_826FE5C0, DrawPrimitiveUP);\n\nGUEST_FUNCTION_HOOK(sub_82547118, CreateVertexDeclaration);\nGUEST_FUNCTION_HOOK(sub_825470F8, SetVertexDeclaration);\n\nGUEST_FUNCTION_HOOK(sub_82548700, CreateVertexShader);\nGUEST_FUNCTION_HOOK(sub_82546EE0, SetVertexShader);\n\nGUEST_FUNCTION_HOOK(sub_82543918, SetStreamSource);\nGUEST_FUNCTION_HOOK(sub_82543AC8, SetIndices);\n\nGUEST_FUNCTION_HOOK(sub_82548608, CreatePixelShader);\nGUEST_FUNCTION_HOOK(sub_82546BD8, SetPixelShader);\n\nGUEST_FUNCTION_HOOK(sub_82636BF8, BeginConditionalSurvey);\nGUEST_FUNCTION_HOOK(sub_82636C08, EndConditionalSurvey);\nGUEST_FUNCTION_HOOK(sub_82636C10, BeginConditionalRendering);\nGUEST_FUNCTION_HOOK(sub_82636C18, EndConditionalRendering);\n\nGUEST_FUNCTION_HOOK(sub_8253B760, IsSet);\n\nGUEST_FUNCTION_HOOK(sub_82543CF0, SetClipPlane);\n\nGUEST_FUNCTION_HOOK(sub_82541A78, SetRenderState<D3DRS_ZENABLE>);\nGUEST_FUNCTION_HOOK(sub_82541AC0, SetRenderState<D3DRS_ZWRITEENABLE>);\nGUEST_FUNCTION_HOOK(sub_82541460, SetRenderState<D3DRS_ALPHATESTENABLE>);\nGUEST_FUNCTION_HOOK(sub_825415C0, SetRenderState<D3DRS_SRCBLEND>);\nGUEST_FUNCTION_HOOK(sub_82541650, SetRenderState<D3DRS_DESTBLEND>);\nGUEST_FUNCTION_HOOK(sub_82541400, SetRenderState<D3DRS_CULLMODE>);\nGUEST_FUNCTION_HOOK(sub_82541AF0, SetRenderState<D3DRS_ZFUNC>);\nGUEST_FUNCTION_HOOK(sub_825418C8, SetRenderState<D3DRS_ALPHAREF>);\nGUEST_FUNCTION_HOOK(sub_825414A0, SetRenderState<D3DRS_ALPHABLENDENABLE>);\nGUEST_FUNCTION_HOOK(sub_82541530, SetRenderState<D3DRS_BLENDOP>);\nGUEST_FUNCTION_HOOK(sub_82543ED0, SetRenderState<D3DRS_SCISSORTESTENABLE>);\nGUEST_FUNCTION_HOOK(sub_82541E90, SetRenderState<D3DRS_SLOPESCALEDEPTHBIAS>);\nGUEST_FUNCTION_HOOK(sub_82541F58, SetRenderState<D3DRS_DEPTHBIAS>);\nGUEST_FUNCTION_HOOK(sub_82541750, SetRenderState<D3DRS_SRCBLENDALPHA>);\nGUEST_FUNCTION_HOOK(sub_825417C0, SetRenderState<D3DRS_DESTBLENDALPHA>);\nGUEST_FUNCTION_HOOK(sub_825416E0, SetRenderState<D3DRS_BLENDOPALPHA>);\nGUEST_FUNCTION_HOOK(sub_82542050, SetRenderState<D3DRS_COLORWRITEENABLE>);\nGUEST_FUNCTION_HOOK(sub_82541B30, SetRenderState<D3DRS_STENCILENABLE>);\nGUEST_FUNCTION_HOOK(sub_82541B78, SetRenderState<D3DRS_TWOSIDEDSTENCILMODE>);\nGUEST_FUNCTION_HOOK(sub_82541BE8, SetRenderState<D3DRS_STENCILFAIL>);\nGUEST_FUNCTION_HOOK(sub_82541C28, SetRenderState<D3DRS_STENCILZFAIL>);\nGUEST_FUNCTION_HOOK(sub_82541C68, SetRenderState<D3DRS_STENCILPASS>);\nGUEST_FUNCTION_HOOK(sub_82541BB8, SetRenderState<D3DRS_STENCILFUNC>);\nGUEST_FUNCTION_HOOK(sub_82541D78, SetRenderState<D3DRS_STENCILREF>);\nGUEST_FUNCTION_HOOK(sub_82541D98, SetRenderState<D3DRS_STENCILMASK>);\nGUEST_FUNCTION_HOOK(sub_82541DB8, SetRenderState<D3DRS_STENCILWRITEMASK>);\nGUEST_FUNCTION_HOOK(sub_82541CC8, SetRenderState<D3DRS_CCW_STENCILFAIL>);\nGUEST_FUNCTION_HOOK(sub_82541D08, SetRenderState<D3DRS_CCW_STENCILZFAIL>);\nGUEST_FUNCTION_HOOK(sub_82541D48, SetRenderState<D3DRS_CCW_STENCILPASS>);\nGUEST_FUNCTION_HOOK(sub_82541C98, SetRenderState<D3DRS_CCW_STENCILFUNC>);\nGUEST_FUNCTION_HOOK(sub_82541E38, SetRenderState<D3DRS_CLIPPLANEENABLE>);\n\nint GetType(GuestResource* resource)\n{\n    if (resource->type == ResourceType::Texture) return 3;\n    if (resource->type == ResourceType::VolumeTexture) return 17;\n    if (resource->type == ResourceType::ArrayTexture) return 19;\n\n    LOGF_WARNING(\"unknown resource type {:d}!\", (int32_t)resource->type);\n    __builtin_trap();\n    return 0;\n}\n\nGUEST_FUNCTION_HOOK(sub_8253AE08, GetType);\n\n// Game asks about the size of surface to check if it needs to be tiled.\n// Because EDRAM has only 10MB, if size is more than 1024, then it enables tiling.\n// We return 0 to always disable tiling.\nint SurfaceSize(uint32_t width, uint32_t height, uint32_t format, uint32_t multisampleLevel)\n{\n    return 0;\n}\n\nGUEST_FUNCTION_HOOK(sub_82538D60, SurfaceSize);\nGUEST_FUNCTION_HOOK(sub_82656B68, MakePictureData);\nGUEST_FUNCTION_HOOK(sub_82656DB8, MakePictureData);\n\n// GUEST_FUNCTION_HOOK(sub_82E9EE38, SetResolution);\n\nGUEST_FUNCTION_HOOK(sub_82736178, ScreenShaderInit);\n\nGUEST_FUNCTION_STUB(sub_8253EB38);\nGUEST_FUNCTION_STUB(sub_8253EB78);\nGUEST_FUNCTION_STUB(sub_82543BE0); // SetGammaRamp\nGUEST_FUNCTION_STUB(sub_82543C68); // SetGammaRamp\nGUEST_FUNCTION_STUB(sub_82547278); // Set shader allocation\nGUEST_FUNCTION_STUB(sub_8272FAD0);\nGUEST_FUNCTION_STUB(sub_82558E00);\nGUEST_FUNCTION_STUB(sub_82559928);\nGUEST_FUNCTION_STUB(sub_82559C18);\nGUEST_FUNCTION_STUB(sub_82700C18); // D3DXFilterTexture\nGUEST_FUNCTION_STUB(sub_8253EAE0);\nGUEST_FUNCTION_STUB(sub_8254D598); // BeginConditional\nGUEST_FUNCTION_STUB(sub_8254D7B0); // BeginConditional\nGUEST_FUNCTION_STUB(sub_8254D9D0); // BeginConditional\nGUEST_FUNCTION_STUB(sub_8254DB90); // BeginConditional\nGUEST_FUNCTION_STUB(sub_8254DD40); // SetScreenExtentQueryMode\n\nstruct Rect\n{\n    be<uint32_t> x1;\n    be<uint32_t> y1;\n    be<uint32_t> x2;\n    be<uint32_t> y2;\n};\n\nstruct RESOLVE_PARAMS\n{\n    be<uint32_t> format;\n    be<uint32_t> unk;\n    be<uint32_t> format2;\n};\n\nint D3DDevice_BeginTiling(GuestDevice* device, uint32_t flags, uint32_t count, Rect* pTileRects, be<float>* pClearColor, float clearZ, uint32_t clearStencil)\n{\n    Clear(device, 0x3F, 0, pClearColor, clearZ, clearStencil);\n\n    return 0;\n}\n\nGUEST_FUNCTION_HOOK(sub_82558F88, D3DDevice_BeginTiling);\n\nint D3DDevice_EndTiling(GuestDevice* device, uint32_t flags, Rect* pResolveRects, GuestTexture* pDestTexture, be<float>* pClearColor, float clearZ, uint32_t clearStencil, RESOLVE_PARAMS* resolveParams)\n{\n    if (pDestTexture)\n    {\n        StretchRect(device, flags, 0, pDestTexture, 0, 0, 0);\n    }\n\n    return 0;\n}\n\nGUEST_FUNCTION_HOOK(sub_82559480, D3DDevice_EndTiling);\n\nint D3DDevice_BeginShaderConstantF4(GuestDevice* device, uint32_t isPixelShader, uint32_t startRegister, be<uint32_t>* cachedConstantData, be<uint32_t>* writeCombinedConstantData, uint32_t vectorCount)\n{\n    uint32_t* constants;\n    be<uint64_t>* dirtyFlags;\n\n    if (isPixelShader)\n    {\n        constants = &device->pixelShaderFloatConstants[startRegister * 4];\n        dirtyFlags = &device->dirtyFlags[1];\n    }\n    else\n    {\n        constants = &device->vertexShaderFloatConstants[startRegister * 4];\n        dirtyFlags = &device->dirtyFlags[0];\n    }\n\n    const uint32_t addr = g_memory.MapVirtual(constants);\n    *cachedConstantData = addr;\n    *writeCombinedConstantData = addr;\n\n    const uint32_t startBit = startRegister >> 2;\n    const uint32_t endBit = (startRegister + vectorCount - 1) >> 2;\n    const uint64_t dirtyFlag = ~0ull << startBit >> startBit >> (63 - endBit) << (63 - endBit);\n    *dirtyFlags = dirtyFlags->get() | dirtyFlag;\n\n    return 0;\n}\n\nGUEST_FUNCTION_HOOK(sub_825466E8, D3DDevice_BeginShaderConstantF4);\n"
  },
  {
    "path": "MarathonRecomp/gpu/video.h",
    "content": "#pragma once\n\n//#define ASYNC_PSO_DEBUG\n/////////////////////////////////////////////////////////////////////#define PSO_CACHING\n//#define PSO_CACHING_CLEANUP\n\n#include <plume_render_interface.h>\n#include <os/logger.h>\n#include <cstdint>\n\n#define D3DCLEAR_TARGET  0x1\n#define D3DCLEAR_ZBUFFER 0x10\n#define D3DCLEAR_STENCIL 0x20\n\n// TODO: remove\n#define SPEC_CONSTANT_ALPHA_TO_COVERAGE (1 << 3)\n#define SPEC_CONSTANT_REVERSE_Z         (1 << 4)\n\n#define LOAD_ZSTD_TEXTURE(name) LoadTexture(decompressZstd(name, name##_uncompressed_size).get(), name##_uncompressed_size)\n\nusing namespace plume;\n\nstruct Video\n{\n    static inline uint32_t s_viewportWidth;\n    static inline uint32_t s_viewportHeight;\n\n    static bool CreateHostDevice(const char *sdlVideoDriver, bool graphicsApiRetry);\n    static void WaitOnSwapChain();\n    static void Present();\n    static void StartPipelinePrecompilation();\n    static void WaitForGPU();\n    static void ComputeViewportDimensions();\n};\n\nenum class Backend {\n    VULKAN,\n    D3D12,\n    METAL\n};\n\nstruct GuestSamplerState\n{\n    be<uint32_t> data[6];\n};\n\nstruct GuestDevice\n{\n    be<uint64_t> dirtyFlags[7]; // 0x0 + 0x38\n\n    be<uint32_t> setRenderStateFunctions[0x61]; // 0x38 + 0x184\n    uint32_t setSamplerStateFunctions[0x14]; // 0x1BC + 0x50\n\n    uint8_t padding20C[0x1F4]; // 0x20C + 0x1F4\n\n    GuestSamplerState samplerStates[0x20]; // 0x400 + 0x300\n\n    uint32_t vertexShaderFloatConstants[0x400]; // 0x700 + 0x1000\n    uint32_t pixelShaderFloatConstants[0x400]; // 0x1700 + 0x1000\n\n    be<uint32_t> vertexShaderBoolConstants[0x4]; // 0x2700 + 0x10\n    be<uint32_t> pixelShaderBoolConstants[0x4]; // 0x2710 + 0x10\n\n    uint8_t padding2720[0x5F0]; // 0x2720 + 0x5F0\n    be<uint32_t> vertexDeclaration; // 0x2D10 + 0x4\n    uint8_t padding2D14[0x344]; // 0x2D14 + 0x344\n    struct\n    {\n        be<float> x;\n        be<float> y;\n        be<float> width;\n        be<float> height;\n        be<float> minZ;\n        be<float> maxZ;\n    } viewport; // 0x3058 + 0x18\n    uint8_t padding3070[0x1F90]; // 0x3070 + 0x1F90\n};\n\nstatic_assert(sizeof(GuestDevice) == 0x5000);\n\nenum class ResourceType\n{\n    Texture,\n    VolumeTexture,\n    ArrayTexture,\n    VertexBuffer,\n    IndexBuffer,\n    RenderTarget,\n    DepthStencil,\n    VertexDeclaration,\n    VertexShader,\n    PixelShader\n};\n\nstruct GuestResource\n{\n    uint32_t unused = 0;\n    be<uint32_t> refCount = 1;\n    ResourceType type;\n\n    GuestResource(ResourceType type) : type(type) \n    {\n    }\n\n    void AddRef()\n    {\n        std::atomic_ref atomicRef(refCount.value);\n\n        uint32_t originalValue, incrementedValue;\n        do\n        {\n            originalValue = refCount.value;\n            incrementedValue = ByteSwap(ByteSwap(originalValue) + 1);\n        } while (!atomicRef.compare_exchange_weak(originalValue, incrementedValue));\n    }\n\n    void Release()\n    {\n        std::atomic_ref atomicRef(refCount.value);\n\n        uint32_t originalValue, decrementedValue;\n        do\n        {\n            originalValue = refCount.value;\n            decrementedValue = ByteSwap(ByteSwap(originalValue) - 1);\n        } while (!atomicRef.compare_exchange_weak(originalValue, decrementedValue));\n\n        // Normally we are supposed to release here, so only use this\n        // function when you know you won't be the one destructing it.\n    }\n};\n\nenum GuestFormat\n{\n    D3DFMT_A16B16G16R16F = 0x1A22AB60,\n    D3DFMT_A16B16G16R16F_2 = 0x1A2201BF,\n    D3DFMT_A16B16G16R16F_EXPAND = 0x1A22AB5D,\n    D3DFMT_DXT1 = 0x1A200152,\n    D3DFMT_DXT4 = 0x1A200154,\n    D3DFMT_A8B8G8R8 = 0x1A200186,\n    D3DFMT_A8R8G8B8 = 0x18280186,\n    D3DFMT_LIN_A8R8G8B8 = 0x18280086,\n    D3DFMT_D24FS8 = 0x1A220197,\n    D3DFMT_D24S8 = 0x2D200196,\n    D3DFMT_R32F = 0x2DA2ABA4,\n    D3DFMT_G16R16F = 0x2D22AB9F,\n    D3DFMT_G16R16F_2 = 0x2D20AB8D,\n    D3DFMT_INDEX16 = 1,\n    D3DFMT_INDEX32 = 6,\n    D3DFMT_A8 = 0x4900102,\n    D3DFMT_L8 = 0x28000102,\n    D3DFMT_L8_2 = 0x28000002,\n    D3DFMT_X8R8G8B8 = 0x28280086,\n    D3DFMT_LE_X8R8G8B8 = 0x28280106,\n    D3DFMT_UNKNOWN = 0xFFFFFFFF\n};\n\nstruct GuestBaseTexture : GuestResource\n{\n    std::unique_ptr<RenderTexture> textureHolder;\n    RenderTexture* texture = nullptr;\n    std::unique_ptr<RenderTextureView> textureView;\n    uint32_t width = 0;\n    uint32_t height = 0;\n    RenderFormat format = RenderFormat::UNKNOWN;\n    uint32_t descriptorIndex = 0;\n    RenderTextureLayout layout = RenderTextureLayout::UNKNOWN;\n\n    GuestBaseTexture(ResourceType type) : GuestResource(type)\n    {\n    }\n};\n\n// Texture/VolumeTexture\nstruct GuestTexture : GuestBaseTexture\n{\n    uint32_t depth = 0;\n    uint32_t mipLevels = 1;\n    RenderTextureViewDimension viewDimension = RenderTextureViewDimension::UNKNOWN;\n    void* mappedMemory = nullptr;\n    ankerl::unordered_dense::map<uint32_t, std::unique_ptr<RenderFramebuffer>> framebuffers;\n    std::vector<std::unique_ptr<RenderTextureView>> framebufferViews;\n    std::unique_ptr<GuestTexture> patchedTexture;\n    struct GuestSurface* sourceSurface = nullptr;\n};\n\nstruct GuestLockedRect\n{\n    be<uint32_t> pitch;\n    be<uint32_t> bits;\n};\n\nstruct GuestBufferDesc\n{\n    be<uint32_t> format;\n    be<uint32_t> type;\n    be<uint32_t> usage;\n    be<uint32_t> pool;\n    be<uint32_t> size;\n    be<uint32_t> fvf;\n};\n\n// VertexBuffer/IndexBuffer\nstruct GuestBuffer : GuestResource\n{\n    std::unique_ptr<RenderBuffer> buffer;\n    void* mappedMemory = nullptr;\n    uint32_t dataSize = 0;\n    RenderFormat format = RenderFormat::UNKNOWN;\n    uint32_t guestFormat = 0;\n    bool lockedReadOnly = false;\n};\n\nstruct GuestSurfaceDesc\n{\n    be<uint32_t> format;\n    be<uint32_t> type;\n    be<uint32_t> usage;\n    be<uint32_t> pool;\n    be<uint32_t> multiSampleType;\n    be<uint32_t> multiSampleQuality;\n    be<uint32_t> width;\n    be<uint32_t> height;\n};\n\nstruct GuestSurfaceCreateParams\n{\n    be<uint32_t> base;\n    be<uint32_t> hzBase;\n    be<int32_t> colorExpBias;\n};\n\n// RenderTarget/DepthStencil\nstruct GuestSurface : GuestBaseTexture\n{\n    uint32_t guestFormat = 0;\n    ankerl::unordered_dense::map<const RenderTexture*, std::unique_ptr<RenderFramebuffer>> framebuffers;\n    RenderSampleCounts sampleCount = RenderSampleCount::COUNT_1;\n    ankerl::unordered_dense::map<GuestTexture*, uint32_t> destinationTextures;\n    bool wasCached = false;\n};\n\nenum GuestDeclType\n{\n    D3DDECLTYPE_FLOAT1 = 0x2C83A4,\n    D3DDECLTYPE_FLOAT2 = 0x2C23A5,\n    D3DDECLTYPE_FLOAT3 = 0x2A23B9,\n    D3DDECLTYPE_FLOAT4 = 0x1A23A6,\n    D3DDECLTYPE_D3DCOLOR = 0x182886,\n    D3DDECLTYPE_UBYTE4 = 0x1A2286,\n    D3DDECLTYPE_UBYTE4_2 = 0x1A2386,\n    D3DDECLTYPE_SHORT2 = 0x2C2359,\n    D3DDECLTYPE_SHORT4 = 0x1A235A,\n    D3DDECLTYPE_UBYTE4N = 0x1A2086,\n    D3DDECLTYPE_UBYTE4N_2 = 0x1A2186,\n    D3DDECLTYPE_SHORT2N = 0x2C2159,\n    D3DDECLTYPE_SHORT4N = 0x1A215A,\n    D3DDECLTYPE_USHORT2N = 0x2C2059,\n    D3DDECLTYPE_USHORT4N = 0x1A205A,\n    D3DDECLTYPE_UINT1 = 0x2C82A1,\n    D3DDECLTYPE_UDEC3 = 0x2A2287,\n    D3DDECLTYPE_DEC3N = 0x2A2187,\n    D3DDECLTYPE_DEC3N_2 = 0x2A2190,\n    D3DDECLTYPE_DEC3N_3 = 0x2A2390,\n    D3DDECLTYPE_FLOAT16_2 = 0x2C235F,\n    D3DDECLTYPE_FLOAT16_4 = 0x1A2360,\n    D3DDECLTYPE_UNUSED = 0xFFFFFFFF\n};\n\nenum GuestDeclUsage\n{\n    D3DDECLUSAGE_POSITION = 0,\n    D3DDECLUSAGE_BLENDWEIGHT = 1,\n    D3DDECLUSAGE_BLENDINDICES = 2,\n    D3DDECLUSAGE_NORMAL = 3,\n    D3DDECLUSAGE_PSIZE = 4,\n    D3DDECLUSAGE_TEXCOORD = 5,\n    D3DDECLUSAGE_TANGENT = 6,\n    D3DDECLUSAGE_BINORMAL = 7,\n    D3DDECLUSAGE_TESSFACTOR = 8,\n    D3DDECLUSAGE_POSITIONT = 9,\n    D3DDECLUSAGE_COLOR = 10,\n    D3DDECLUSAGE_FOG = 11,\n    D3DDECLUSAGE_DEPTH = 12,\n    D3DDECLUSAGE_SAMPLE = 13\n};\n\nstruct GuestVertexElement\n{\n    be<uint16_t> stream;\n    be<uint16_t> offset;\n    be<uint32_t> type;\n    uint8_t method;\n    uint8_t usage;\n    uint8_t usageIndex;\n    uint8_t padding;\n};\n\n#define D3DDECL_END() { 255, 0, 0xFFFFFFFF, 0, 0, 0 }\n\nstruct GuestVertexDeclaration : GuestResource\n{\n    XXH64_hash_t hash = 0;\n    std::unique_ptr<RenderInputElement[]> inputElements;\n    std::unique_ptr<GuestVertexElement[]> vertexElements;\n    uint32_t inputElementCount = 0;\n    uint32_t vertexElementCount = 0;\n    uint32_t swappedTexcoords = 0;\n    uint32_t swappedNormals = 0;\n    uint32_t swappedBinormals = 0;\n    uint32_t swappedTangents = 0;\n    uint32_t swappedBlendWeights = 0;\n    bool hasR11G11B10Normal = false;\n    bool vertexStreams[16]{};\n    uint32_t indexVertexStream = 0;\n};\n\n// VertexShader/PixelShader\nstruct GuestShader : GuestResource\n{\n    Mutex mutex;\n    std::unique_ptr<RenderShader> shader;\n    struct ShaderCacheEntry* shaderCacheEntry = nullptr;\n    ankerl::unordered_dense::map<uint32_t, std::unique_ptr<RenderShader>> linkedShaders;\n#ifdef MARATHON_RECOMP_D3D12\n    std::vector<ComPtr<IDxcBlob>> shaderBlobs;\n    ComPtr<IDxcBlobEncoding> libraryBlob;\n#endif\n#ifdef ASYNC_PSO_DEBUG\n    const char* name = \"<unknown>\";\n#endif\n};\n\nstruct GuestViewport\n{\n    be<uint32_t> x;\n    be<uint32_t> y;\n    be<uint32_t> width;\n    be<uint32_t> height;\n    be<float> minZ;\n    be<float> maxZ;\n};\n\nstruct GuestRect\n{\n    be<int32_t> left;\n    be<int32_t> top;\n    be<int32_t> right;\n    be<int32_t> bottom;\n};\n\nenum GuestRenderState\n{\n    D3DRS_ZENABLE = 40,\n    D3DRS_ZFUNC = 44,\n    D3DRS_ZWRITEENABLE = 48,\n    D3DRS_CULLMODE = 56,\n    D3DRS_ALPHABLENDENABLE = 60,\n    D3DRS_SRCBLEND = 72,\n    D3DRS_DESTBLEND = 76,\n    D3DRS_BLENDOP = 80,\n    D3DRS_SRCBLENDALPHA = 84,\n    D3DRS_DESTBLENDALPHA = 88,\n    D3DRS_BLENDOPALPHA = 92,\n    D3DRS_ALPHATESTENABLE = 96,\n    D3DRS_ALPHAREF = 100,\n    D3DRS_STENCILENABLE = 108,\n    D3DRS_TWOSIDEDSTENCILMODE = 112,\n    D3DRS_STENCILFAIL = 116,\n    D3DRS_STENCILZFAIL = 120,\n    D3DRS_STENCILPASS = 124,\n    D3DRS_STENCILFUNC = 128,\n    D3DRS_STENCILREF = 132,\n    D3DRS_STENCILMASK = 136,\n    D3DRS_STENCILWRITEMASK = 140,\n    D3DRS_CCW_STENCILFAIL = 144,\n    D3DRS_CCW_STENCILZFAIL = 148,\n    D3DRS_CCW_STENCILPASS = 152,\n    D3DRS_CCW_STENCILFUNC = 156,\n    D3DRS_CLIPPLANEENABLE = 172,\n    D3DRS_SCISSORTESTENABLE = 200,\n    D3DRS_SLOPESCALEDEPTHBIAS = 204,\n    D3DRS_DEPTHBIAS = 208,\n    D3DRS_COLORWRITEENABLE = 212\n};\n\nenum GuestCullMode\n{\n    D3DCULL_NONE_CCW = 0,\n    D3DCULL_FRONT_CCW = 1,\n    D3DCULL_BACK_CCW = 2,\n    D3DCULL_NONE_CW = 4,\n    D3DCULL_FRONT_CW = 5,\n    D3DCULL_BACK_CW = 6\n};\n\nenum GuestBlendMode\n{\n    D3DBLEND_ZERO = 0,\n    D3DBLEND_ONE = 1,\n    D3DBLEND_SRCCOLOR = 4,\n    D3DBLEND_INVSRCCOLOR = 5,\n    D3DBLEND_SRCALPHA = 6,\n    D3DBLEND_INVSRCALPHA = 7,\n    D3DBLEND_DESTCOLOR = 8,\n    D3DBLEND_INVDESTCOLOR = 9,\n    D3DBLEND_DESTALPHA = 10,\n    D3DBLEND_INVDESTALPHA = 11\n};\n\nenum GuestBlendOp\n{\n    D3DBLENDOP_ADD = 0,\n    D3DBLENDOP_SUBTRACT = 1,\n    D3DBLENDOP_MIN = 2,\n    D3DBLENDOP_MAX = 3,\n    D3DBLENDOP_REVSUBTRACT = 4\n};\n\nenum GuestCmpFunc\n{\n    D3DCMP_NEVER = 0,\n    D3DCMP_LESS = 1,\n    D3DCMP_EQUAL = 2,\n    D3DCMP_LESSEQUAL = 3,\n    D3DCMP_GREATER = 4,\n    D3DCMP_NOTEQUAL = 5,\n    D3DCMP_GREATEREQUAL = 6,\n    D3DCMP_ALWAYS = 7\n};\n\nenum GuestStencilOp\n{\n    D3DSTENCILOP_KEEP = 0,\n    D3DSTENCILOP_ZERO = 1,\n    D3DSTENCILOP_REPLACE = 2,\n    D3DSTENCILOP_INCRSAT = 3,\n    D3DSTENCILOP_DECRSAT = 4,\n    D3DSTENCILOP_INVERT = 5,\n    D3DSTENCILOP_INCR = 6,\n    D3DSTENCILOP_DECR = 7\n};\n\nenum GuestPrimitiveType\n{\n    D3DPT_POINTLIST = 1,\n    D3DPT_LINELIST = 2,\n    D3DPT_LINESTRIP = 3,\n    D3DPT_TRIANGLELIST = 4,\n    D3DPT_TRIANGLEFAN = 5,\n    D3DPT_TRIANGLESTRIP = 6,\n    D3DPT_QUADLIST = 13\n};\n\nenum GuestTextureFilterType\n{\n    D3DTEXF_POINT = 0,\n    D3DTEXF_LINEAR = 1,\n    D3DTEXF_NONE = 2\n};\n\nenum GuestTextureAddress\n{\n    D3DTADDRESS_WRAP = 0,\n    D3DTADDRESS_MIRROR = 1,\n    D3DTADDRESS_CLAMP = 2,\n    D3DTADDRESS_MIRRORONCE = 3,\n    D3DTADDRESS_BORDER = 6\n};\n\ninline bool g_needsResize;\n\nextern std::unique_ptr<GuestTexture> LoadTexture(const uint8_t* data, size_t dataSize, RenderComponentMapping componentMapping = RenderComponentMapping());\n\nextern void VideoConfigValueChangedCallback(class IConfigDef* config);\n"
  },
  {
    "path": "MarathonRecomp/hid/driver/sdl_hid.cpp",
    "content": "#include <stdafx.h>\n#include <SDL.h>\n#include <user/config.h>\n#include <hid/hid.h>\n#include <os/logger.h>\n#include <ui/game_window.h>\n#include <kernel/xdm.h>\n#include <app.h>\n\n#define TRANSLATE_INPUT(S, X) SDL_GameControllerGetButton(controller, S) << FirstBitLow(X)\n#define VIBRATION_TIMEOUT_MS 5000\n\nclass Controller\n{\npublic:\n    SDL_GameController* controller{};\n    SDL_Joystick* joystick{};\n    SDL_JoystickID id{ -1 };\n    XAMINPUT_GAMEPAD state{};\n    XAMINPUT_VIBRATION vibration{ 0, 0 };\n    int index{};\n\n    Controller() = default;\n\n    explicit Controller(int index) : Controller(SDL_GameControllerOpen(index))\n    {\n        this->index = index;\n    }\n\n    Controller(SDL_GameController* controller) : controller(controller)\n    {\n        if (!controller)\n            return;\n\n        joystick = SDL_GameControllerGetJoystick(controller);\n        id = SDL_JoystickInstanceID(joystick);\n    }\n\n    SDL_GameControllerType GetControllerType() const\n    {\n        return SDL_GameControllerGetType(controller);\n    }\n\n    hid::EInputDevice GetInputDevice() const\n    {\n        switch (GetControllerType())\n        {\n            case SDL_CONTROLLER_TYPE_PS3:\n            case SDL_CONTROLLER_TYPE_PS4:\n            case SDL_CONTROLLER_TYPE_PS5:\n                return hid::EInputDevice::PlayStation;\n            case SDL_CONTROLLER_TYPE_XBOX360:\n            case SDL_CONTROLLER_TYPE_XBOXONE:\n                return hid::EInputDevice::Xbox;\n            default:\n                return hid::EInputDevice::Unknown;\n        }\n    }\n\n    const char* GetControllerName() const\n    {\n        auto result = SDL_GameControllerName(controller);\n\n        if (!result)\n            return \"Unknown Device\";\n\n        return result;\n    }\n\n    void Close()\n    {\n        if (!controller)\n            return;\n\n        SDL_GameControllerClose(controller);\n\n        controller = nullptr;\n        joystick = nullptr;\n        id = -1;\n    }\n\n    bool CanPoll()\n    {\n        return controller;\n    }\n\n    void PollAxis()\n    {\n        if (!CanPoll())\n            return;\n\n        auto& pad = state;\n\n        pad.sThumbLX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);\n        pad.sThumbLY = ~SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);\n\n        pad.sThumbRX = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX);\n        pad.sThumbRY = ~SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY);\n\n        pad.bLeftTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) >> 7;\n        pad.bRightTrigger = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) >> 7;\n    }\n\n    void Poll()\n    {\n        if (!CanPoll())\n            return;\n\n        auto& pad = state;\n\n        pad.wButtons = 0;\n\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_UP, XAMINPUT_GAMEPAD_DPAD_UP);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_DOWN, XAMINPUT_GAMEPAD_DPAD_DOWN);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_LEFT, XAMINPUT_GAMEPAD_DPAD_LEFT);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_DPAD_RIGHT, XAMINPUT_GAMEPAD_DPAD_RIGHT);\n\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_START, XAMINPUT_GAMEPAD_START);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_BACK, XAMINPUT_GAMEPAD_BACK);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_TOUCHPAD, XAMINPUT_GAMEPAD_BACK);\n\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_LEFTSTICK, XAMINPUT_GAMEPAD_LEFT_THUMB);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_RIGHTSTICK, XAMINPUT_GAMEPAD_RIGHT_THUMB);\n\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_LEFTSHOULDER, XAMINPUT_GAMEPAD_LEFT_SHOULDER);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, XAMINPUT_GAMEPAD_RIGHT_SHOULDER);\n\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_A, XAMINPUT_GAMEPAD_A);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_B, XAMINPUT_GAMEPAD_B);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_X, XAMINPUT_GAMEPAD_X);\n        pad.wButtons |= TRANSLATE_INPUT(SDL_CONTROLLER_BUTTON_Y, XAMINPUT_GAMEPAD_Y);\n    }\n\n    void SetVibration(const XAMINPUT_VIBRATION& vibration)\n    {\n        if (!CanPoll())\n            return;\n\n        this->vibration = vibration;\n\n        SDL_GameControllerRumble(controller, vibration.wLeftMotorSpeed * 256, vibration.wRightMotorSpeed * 256, VIBRATION_TIMEOUT_MS);\n    }\n\n    void SetLED(const uint8_t r, const uint8_t g, const uint8_t b) const\n    {\n        SDL_GameControllerSetLED(controller, r, g, b);\n    }\n};\n\nstd::array<Controller, 4> g_controllers;\nController* g_activeController;\n\ninline Controller* EnsureController(uint32_t dwUserIndex)\n{\n    if (!g_controllers[dwUserIndex].controller)\n        return nullptr;\n\n    return &g_controllers[dwUserIndex];\n}\n\ninline size_t FindFreeController()\n{\n    for (size_t i = 0; i < g_controllers.size(); i++)\n    {\n        if (!g_controllers[i].controller)\n            return i;\n    }\n\n    return -1;\n}\n\ninline Controller* FindController(int which)\n{\n    for (auto& controller : g_controllers)\n    {\n        if (controller.id == which)\n            return &controller;\n    }\n\n    return nullptr;\n}\n\nstatic void SetControllerInputDevice(Controller* controller)\n{\n    g_activeController = controller;\n\n    if (App::s_isLoading)\n        return;\n\n    hid::g_inputDevice = controller->GetInputDevice();\n    hid::g_inputDeviceController = hid::g_inputDevice;\n\n    auto controllerType = (hid::EInputDeviceExplicit)controller->GetControllerType();\n    auto controllerName = controller->GetControllerName();\n\n    // Only proceed if the controller type changes.\n    if (hid::g_inputDeviceExplicit != controllerType)\n    {\n        hid::g_inputDeviceExplicit = controllerType;\n\n        if (controllerType == hid::EInputDeviceExplicit::Unknown)\n        {\n            LOGFN(\"Detected controller: {} (Unknown Controller Type)\", controllerName);\n        }\n        else\n        {\n            LOGFN(\"Detected controller: {}\", controllerName);\n        }\n    }\n}\n\nstatic void SetControllerTimeOfDayLED(Controller& controller, EPlayerCharacter player)\n{\n    uint8_t r, g, b;\n\n    // TODO: Per-character colors\n\n    switch (player) {\n        case EPlayerCharacter::Sonic:\n            break;\n        case EPlayerCharacter::Shadow:\n            break;\n        case EPlayerCharacter::Silver:\n            break;\n        case EPlayerCharacter::Blaze:\n            break;\n        case EPlayerCharacter::Amy:\n            break;\n        case EPlayerCharacter::Tails:\n            break;\n        case EPlayerCharacter::Rouge:\n            break;\n        case EPlayerCharacter::Knuckles:\n            break;\n    }\n\n    r = 0;\n    g = 37;\n    b = 184;\n\n    controller.SetLED(r, g, b);\n}\n\nint HID_OnSDLEvent(void*, SDL_Event* event)\n{\n    switch (event->type)\n    {\n        case SDL_CONTROLLERDEVICEADDED:\n        {\n            const auto freeIndex = FindFreeController();\n\n            if (freeIndex != -1)\n            {\n                auto controller = Controller(event->cdevice.which);\n\n                g_controllers[freeIndex] = controller;\n\n                SetControllerTimeOfDayLED(controller, App::s_playerCharacter);\n            }\n\n            break;\n        }\n\n        case SDL_CONTROLLERDEVICEREMOVED:\n        {\n            auto* controller = FindController(event->cdevice.which);\n\n            if (controller)\n                controller->Close();\n\n            break;\n        }\n\n        case SDL_CONTROLLERBUTTONDOWN:\n        case SDL_CONTROLLERBUTTONUP:\n        case SDL_CONTROLLERAXISMOTION:\n        case SDL_CONTROLLERTOUCHPADDOWN:\n        {\n            auto* controller = FindController(event->cdevice.which);\n\n            if (!controller)\n                break;\n\n            if (event->type == SDL_CONTROLLERAXISMOTION)\n            {\n                if (abs(event->caxis.value) > 8000)\n                {\n                    SDL_ShowCursor(SDL_DISABLE);\n                    SetControllerInputDevice(controller);\n                }\n\n                controller->PollAxis();\n            }\n            else\n            {\n                SDL_ShowCursor(SDL_DISABLE);\n                SetControllerInputDevice(controller);\n\n                controller->Poll();\n            }\n\n            break;\n        }\n\n        case SDL_KEYDOWN:\n        case SDL_KEYUP:\n            hid::g_inputDevice = hid::EInputDevice::Keyboard;\n            break;\n\n        case SDL_MOUSEMOTION:\n        case SDL_MOUSEBUTTONDOWN:\n        case SDL_MOUSEBUTTONUP:\n        {\n            if (!GameWindow::IsFullscreen() || GameWindow::s_isFullscreenCursorVisible)\n                SDL_ShowCursor(SDL_ENABLE);\n\n            hid::g_inputDevice = hid::EInputDevice::Mouse;\n\n            break;\n        }\n\n        case SDL_WINDOWEVENT:\n        {\n            if (event->window.event == SDL_WINDOWEVENT_FOCUS_LOST)\n            {\n                // Stop vibrating controllers on focus lost.\n                for (auto& controller : g_controllers)\n                    controller.SetVibration({ 0, 0 });\n            }\n\n            break;\n        }\n\n        case SDL_USER_PLAYER_CHAR:\n        {\n            for (auto& controller : g_controllers)\n                SetControllerTimeOfDayLED(controller, static_cast<EPlayerCharacter>(event->user.code));\n\n            break;\n        }\n    }\n\n    return 0;\n}\n\nvoid hid::Init()\n{\n    SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS3, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_WII, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, \"1\");\n    SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAMDECK, \"1\");\n    SDL_SetHint(SDL_HINT_XINPUT_ENABLED, \"1\");\n    \n    SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, \"0\"); // Uses Button Labels. This hint is disabled for Nintendo Controllers.\n\n    SDL_InitSubSystem(SDL_INIT_EVENTS);\n    SDL_AddEventWatch(HID_OnSDLEvent, nullptr);\n\n    SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER);\n\n    // Load controller mappings from SDL_GameControllerDB\n    if (int mappings = SDL_GameControllerAddMappingsFromFile(\"gamecontrollerdb.txt\"); mappings > 0) {\n        LOGFN(\"Loaded {} controller mapping(s) from SDL_GameControllerDB ({})\", mappings, \"gamecontrollerdb.txt\");\n    }\n}\n\nuint32_t hid::GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState)\n{\n    static uint32_t packet;\n\n    if (!pState)\n        return ERROR_BAD_ARGUMENTS;\n\n    memset(pState, 0, sizeof(*pState));\n\n    pState->dwPacketNumber = packet++;\n\n    if (!g_activeController)\n        return ERROR_DEVICE_NOT_CONNECTED;\n\n    pState->Gamepad = g_activeController->state;\n\n    return ERROR_SUCCESS;\n}\n\nuint32_t hid::SetState(uint32_t dwUserIndex, XAMINPUT_VIBRATION* pVibration)\n{\n    if (!pVibration)\n        return ERROR_BAD_ARGUMENTS;\n\n    if (!g_activeController)\n        return ERROR_DEVICE_NOT_CONNECTED;\n\n    g_activeController->SetVibration(*pVibration);\n\n    return ERROR_SUCCESS;\n}\n\nuint32_t hid::GetCapabilities(uint32_t dwUserIndex, XAMINPUT_CAPABILITIES* pCaps)\n{\n    if (!pCaps)\n        return ERROR_BAD_ARGUMENTS;\n\n    if (!g_activeController)\n        return ERROR_DEVICE_NOT_CONNECTED;\n\n    memset(pCaps, 0, sizeof(*pCaps));\n\n    pCaps->Type = XAMINPUT_DEVTYPE_GAMEPAD;\n    pCaps->SubType = XAMINPUT_DEVSUBTYPE_GAMEPAD; // TODO: other types?\n    pCaps->Flags = 0;\n    pCaps->Gamepad = g_activeController->state;\n    pCaps->Vibration = g_activeController->vibration;\n\n    return ERROR_SUCCESS;\n}\n"
  },
  {
    "path": "MarathonRecomp/hid/hid.cpp",
    "content": "#include \"hid.h\"\n#include <ui/game_window.h>\n#include <user/config.h>\n\nhid::EInputDevice hid::g_inputDevice;\nhid::EInputDevice hid::g_inputDeviceController;\nhid::EInputDeviceExplicit hid::g_inputDeviceExplicit;\n\nuint16_t hid::g_prohibitedButtons;\nbool hid::g_isLeftStickProhibited;\nbool hid::g_isRightStickProhibited;\n\nvoid hid::SetProhibitedInputs(uint16_t wButtons, bool leftStick, bool rightStick)\n{\n    hid::g_prohibitedButtons = wButtons;\n    hid::g_isLeftStickProhibited = leftStick;\n    hid::g_isRightStickProhibited = rightStick;\n}\n\nbool hid::IsInputAllowed()\n{\n    return GameWindow::s_isFocused || Config::AllowBackgroundInput;\n}\n\nbool hid::IsInputDeviceController()\n{\n    return hid::g_inputDevice != hid::EInputDevice::Keyboard &&\n        hid::g_inputDevice != hid::EInputDevice::Mouse;\n}\n"
  },
  {
    "path": "MarathonRecomp/hid/hid.h",
    "content": "#pragma once\n\nnamespace hid\n{\n    enum class EInputDevice\n    {\n        Unknown,\n        Keyboard,\n        Mouse,\n        Xbox,\n        PlayStation\n    };\n\n    enum class EInputDeviceExplicit\n    {\n        Unknown,\n        Xbox360,\n        XboxOne,\n        DualShock3,\n        DualShock4,\n        SwitchPro,\n        Virtual,\n        DualSense,\n        Luna,\n        Stadia,\n        NvShield,\n        SwitchJCLeft,\n        SwitchJCRight,\n        SwitchJCPair\n    };\n\n    extern EInputDevice g_inputDevice;\n    extern EInputDevice g_inputDeviceController;\n    extern EInputDeviceExplicit g_inputDeviceExplicit;\n\n    extern uint16_t g_prohibitedButtons;\n    extern bool g_isLeftStickProhibited;\n    extern bool g_isRightStickProhibited;\n\n    void Init();\n\n    uint32_t GetState(uint32_t dwUserIndex, XAMINPUT_STATE* pState);\n    uint32_t SetState(uint32_t dwUserIndex, XAMINPUT_VIBRATION* pVibration);\n    uint32_t GetCapabilities(uint32_t dwUserIndex, XAMINPUT_CAPABILITIES* pCaps);\n\n    void SetProhibitedInputs(uint16_t wButtons = 0, bool leftStick = false, bool rightStick = false);\n    bool IsInputAllowed();\n    bool IsInputDeviceController();\n}\n"
  },
  {
    "path": "MarathonRecomp/install/directory_file_system.h",
    "content": "#pragma once\n\n#include <filesystem>\n\n#include \"virtual_file_system.h\"\n\nstruct DirectoryFileSystem : VirtualFileSystem\n{\n    std::filesystem::path directoryPath;\n    std::string name;\n\n    DirectoryFileSystem(const std::filesystem::path &directoryPath)\n    {\n        this->directoryPath = directoryPath;\n        name = (const char *)(directoryPath.filename().u8string().data());\n    }\n\n    bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const override\n    {\n        std::ifstream fileStream(directoryPath / std::filesystem::path(std::u8string_view((const char8_t *)(path.c_str()))), std::ios::binary);\n        if (fileStream.is_open())\n        {\n            fileStream.read((char *)(fileData), fileDataMaxByteCount);\n            return !fileStream.bad();\n        }\n        else\n        {\n            return false;\n        }\n    }\n\n    size_t getSize(const std::string &path) const override\n    {\n        std::error_code ec;\n        size_t fileSize = std::filesystem::file_size(directoryPath / std::filesystem::path(std::u8string_view((const char8_t *)(path.c_str()))), ec);\n        if (!ec)\n        {\n            return fileSize;\n        }\n        else\n        {\n            return 0;\n        }\n    }\n\n    bool exists(const std::string &path) const override\n    {\n        if (path.empty())\n        {\n            return false;\n        }\n\n        return std::filesystem::exists(directoryPath / std::filesystem::path(std::u8string_view((const char8_t *)(path.c_str()))));\n    }\n\n    const std::string &getName() const override\n    {\n        return name;\n    }\n\n    static std::unique_ptr<VirtualFileSystem> create(const std::filesystem::path &directoryPath)\n    {\n        if (std::filesystem::exists(directoryPath))\n        {\n            return std::make_unique<DirectoryFileSystem>(directoryPath);\n        }\n        else\n        {\n            return nullptr;\n        }\n    }\n};\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_amigo.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t EpisodeAmigoHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeAmigoFiles[];\nextern const size_t EpisodeAmigoFilesSize;\n\nconst uint64_t EpisodeAmigoHashes[] = {\n    8983387072646869383ULL,\n};\n\nconst std::pair<const char *, uint32_t> EpisodeAmigoFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t EpisodeAmigoFilesSize = std::size(EpisodeAmigoFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_amigo.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t EpisodeAmigoHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeAmigoFiles[];\nextern const size_t EpisodeAmigoFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_shadow.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t EpisodeShadowHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeShadowFiles[];\nextern const size_t EpisodeShadowFilesSize;\n\nconst uint64_t EpisodeShadowHashes[] = {\n    10539656742164611611ULL,\n};\n\nconst std::pair<const char *, uint32_t> EpisodeShadowFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t EpisodeShadowFilesSize = std::size(EpisodeShadowFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_shadow.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t EpisodeShadowHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeShadowFiles[];\nextern const size_t EpisodeShadowFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_silver.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t EpisodeSilverHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeSilverFiles[];\nextern const size_t EpisodeSilverFilesSize;\n\nconst uint64_t EpisodeSilverHashes[] = {\n    12996835580564401141ULL,\n};\n\nconst std::pair<const char *, uint32_t> EpisodeSilverFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t EpisodeSilverFilesSize = std::size(EpisodeSilverFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_silver.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t EpisodeSilverHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeSilverFiles[];\nextern const size_t EpisodeSilverFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_sonic.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t EpisodeSonicHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeSonicFiles[];\nextern const size_t EpisodeSonicFilesSize;\n\nconst uint64_t EpisodeSonicHashes[] = {\n    15073381587123252911ULL,\n};\n\nconst std::pair<const char *, uint32_t> EpisodeSonicFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t EpisodeSonicFilesSize = std::size(EpisodeSonicFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/episode_sonic.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t EpisodeSonicHashes[];\nextern const std::pair<const char *, uint32_t> EpisodeSonicFiles[];\nextern const size_t EpisodeSonicFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/game.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t GameHashes[];\nextern const std::pair<const char *, uint32_t> GameFiles[];\nextern const size_t GameFilesSize;\n\nconst uint64_t GameHashes[] = {\n    8351993017922532997ULL,\n    10387650078945694205ULL,\n    15918252288161138853ULL,\n    5452730958083469041ULL,\n    14864687957236683217ULL,\n    4588398277622789161ULL,\n    1122549444656674671ULL,\n    5885697728922631499ULL,\n    15635110676280851876ULL,\n    3502215997315919051ULL,\n    3287508961237706537ULL,\n    2383765786859750683ULL,\n    18013096267158586365ULL,\n    5378233217385144162ULL,\n    8984567973277414284ULL,\n    4068825697758009312ULL,\n    8912993163194524353ULL,\n    6766364709494587624ULL,\n    5165203281591868776ULL,\n    3293546646423584708ULL,\n    510127123707132584ULL,\n    613421661350354339ULL,\n    8615725139300220084ULL,\n    418985277142969910ULL,\n    4065681052882002442ULL,\n    12554136121954960480ULL,\n    5703744011735507919ULL,\n    3934176164980133346ULL,\n    15390830978020201124ULL,\n    12325455430664520125ULL,\n    7189412297617331727ULL,\n    12946235816270579118ULL,\n    9106277541578886813ULL,\n    2649699473170536930ULL,\n    3207664458238798524ULL,\n    6052968602963894858ULL,\n    7644143475127806468ULL,\n    15966464809890326996ULL,\n    1776567654481657012ULL,\n    11700237696799569734ULL,\n    1552082824368854486ULL,\n    9409374462197609982ULL,\n    10329846675422344494ULL,\n    11345124900216766569ULL,\n    9789117714488434491ULL,\n    1130609446972947655ULL,\n    10189235726002196119ULL,\n    14884654433672021891ULL,\n    3078946837915525363ULL,\n    5960606366973278978ULL,\n    8970327760245634221ULL,\n    1838668412725390170ULL,\n    6542730983527968814ULL,\n    8754778863202595052ULL,\n    3193047768790022953ULL,\n    3750338081570554493ULL,\n    6524494994971351633ULL,\n    3432804745839785609ULL,\n    12024435344793110587ULL,\n    5607066874173525902ULL,\n    7261493453087927145ULL,\n    16852230790612353056ULL,\n    13610669732030524206ULL,\n    13255324417898391615ULL,\n    5595794218955118561ULL,\n    6483332957420836746ULL,\n    16456289603706621603ULL,\n    2047510546540242729ULL,\n    6079071265208189465ULL,\n    7320675267255988329ULL,\n    15396952465157254266ULL,\n    3264076318155180729ULL,\n    7382681621572307165ULL,\n    772079045529068376ULL,\n    14011921409579744145ULL,\n    12607615649685981548ULL,\n    4733996493025490240ULL,\n    10777260466079228293ULL,\n    14501850336834917032ULL,\n    17458338278051450418ULL,\n    12107062460763034809ULL,\n    17878170400214505618ULL,\n    804424115290410973ULL,\n    6020937142647651946ULL,\n    16436339236514980274ULL,\n    511862674129498780ULL,\n    16741000990776207374ULL,\n    928481540120459528ULL,\n    7370069239886430319ULL,\n    8747513997169024192ULL,\n    11034232791427722203ULL,\n    10021372195749921053ULL,\n    14697917391429243980ULL,\n    3966379819362750901ULL,\n    2536061930605766565ULL,\n    9725840255691789095ULL,\n    5390405305020820684ULL,\n    370342763391282173ULL,\n    2982922091176469641ULL,\n    5906229564509266432ULL,\n    3092996426725391072ULL,\n    4333936066623409741ULL,\n    2646727979847321182ULL,\n    195392020737512422ULL,\n    7523222828581704438ULL,\n    12895271494233636735ULL,\n    13352948425944629144ULL,\n    15394456931489523284ULL,\n    4701887762849620066ULL,\n    7627468992402108220ULL,\n    16634461619475414265ULL,\n    2171484057264913788ULL,\n    13610880881124644837ULL,\n    5922225356576124855ULL,\n    3305237611592946770ULL,\n    8305257945785296319ULL,\n    1141897396282710972ULL,\n    7259082901179380511ULL,\n    7909907131794486222ULL,\n    14594395054266605131ULL,\n    8911994021944730625ULL,\n    10902447751954560358ULL,\n    16985144179030585540ULL,\n    17696116671783294156ULL,\n    14454353297407543564ULL,\n    13355133586748309097ULL,\n    14777399127630626024ULL,\n    7930321383281062356ULL,\n    15610375919952488152ULL,\n    7797133254038529740ULL,\n    6265295992650116858ULL,\n    14579554764499604523ULL,\n    16328904786231236960ULL,\n    6311998956955426050ULL,\n    752940130727856220ULL,\n    10123616274779016716ULL,\n    15201238633782710748ULL,\n    14195309807539611921ULL,\n    7053636052550479527ULL,\n    971854982639197781ULL,\n    8053554397147962679ULL,\n    17961415202203662445ULL,\n    8761047933840997261ULL,\n    2555362181692540322ULL,\n    10052140421786278741ULL,\n    8324018035539291015ULL,\n    11732492012294063386ULL,\n    1060395877769130600ULL,\n    7490164146285607869ULL,\n    17347035194327999920ULL,\n    10970444186857735374ULL,\n    10713098883821240959ULL,\n    6060469365520758136ULL,\n    1007996608267344047ULL,\n    17786062467752834965ULL,\n    8308211700061525638ULL,\n    330833553673396567ULL,\n    14263372326684114630ULL,\n    1595882167377053882ULL,\n    860509703444667222ULL,\n    15350623473935087737ULL,\n    6003127439812853013ULL,\n    900736905047689077ULL,\n    11981871784046923216ULL,\n    343920880078991689ULL,\n    5400819071441313977ULL,\n    10425183265000600948ULL,\n    11892774542645108826ULL,\n    10929080142559128578ULL,\n    16941577763568258322ULL,\n    5001906873105165075ULL,\n    17333809535728451083ULL,\n    10630796073354098680ULL,\n    13998846740819899403ULL,\n    6352941851781657631ULL,\n    13998846740819899403ULL,\n    6835637137171575367ULL,\n    4714033806257021998ULL,\n    5447243784357251367ULL,\n    527685082572578420ULL,\n    16501737254297182334ULL,\n    3504869225508502420ULL,\n    18440098858438145080ULL,\n    15319453356909134059ULL,\n    834479186828377545ULL,\n    6129625056462738972ULL,\n    6385369917591838684ULL,\n    14150304350342962897ULL,\n    11842610431187615496ULL,\n    2800616125411869998ULL,\n    15511071891176753974ULL,\n    5059832060453959225ULL,\n    7886907665265515499ULL,\n    1712053747295426785ULL,\n    214536915690750489ULL,\n    771267855322846142ULL,\n    6922242629611452842ULL,\n    16369817751108526943ULL,\n    9679152833804734962ULL,\n    9417058359022793302ULL,\n    7366452427950478661ULL,\n    11880277920847728254ULL,\n    10797292878867928790ULL,\n    8734347318430424871ULL,\n    15897617231465261294ULL,\n    4431970558376936770ULL,\n    17285867978297205014ULL,\n    10175833537042922619ULL,\n    14792054015873360099ULL,\n    6486687770728031052ULL,\n    7769161069809673679ULL,\n    17265231622476163706ULL,\n    8236739112385361578ULL,\n    4749911919430749691ULL,\n    16148046852539936283ULL,\n    8337502132484112407ULL,\n    6971864254374112343ULL,\n    8907485454094133691ULL,\n    15262059634092425648ULL,\n    12570881619523088366ULL,\n    13994637630861496093ULL,\n    16722836027407925222ULL,\n    9830020327156033816ULL,\n    5000351185284949845ULL,\n    17961330976997968352ULL,\n    1538249057805925204ULL,\n    12570359742632133486ULL,\n    5685453696808446751ULL,\n    9061023056464070067ULL,\n    14120929683148910405ULL,\n    3309345275404648166ULL,\n    9557892360948858987ULL,\n    1623775785912254333ULL,\n    6944543130363118091ULL,\n    4659127017762284819ULL,\n    6022545066232005794ULL,\n    15087159717305837093ULL,\n    3374467977359598711ULL,\n    14693241743572139806ULL,\n    17308043008373190806ULL,\n    15195500724930505205ULL,\n    12926040652589884774ULL,\n    2115384034026814755ULL,\n    3842191154210892955ULL,\n    17018225194563796498ULL,\n    746287388239867709ULL,\n    6027860221594996801ULL,\n    17253954969921499058ULL,\n    12880262150502530819ULL,\n    1058521587021607252ULL,\n    4062725745426720610ULL,\n    3187865372728508958ULL,\n    16454363834208213770ULL,\n    9632090572553419359ULL,\n    7971169413093629601ULL,\n    5725558575239945301ULL,\n    11877976377918257900ULL,\n    2574645338640871653ULL,\n    3423294849739679677ULL,\n    13556435884917767732ULL,\n    9966181774430414379ULL,\n    207089284894156761ULL,\n    12875290231482699433ULL,\n    899740447794318627ULL,\n    15952018783761653845ULL,\n    7667824642640720291ULL,\n    7427886174990410944ULL,\n    9584074679644931198ULL,\n    18399567836127700676ULL,\n    1070491601910802140ULL,\n    2936342388114571501ULL,\n    9316632470217813398ULL,\n    10029658582354536011ULL,\n    5748174535284053019ULL,\n    17999598461353987592ULL,\n    8745706138394892546ULL,\n    17615441877518324607ULL,\n    7403692096756338831ULL,\n    5165279385045870017ULL,\n    7909554987934377177ULL,\n    6336194158006475280ULL,\n    14111472374585243957ULL,\n    7348584054841674494ULL,\n    12006092364105360437ULL,\n    9249372970899913977ULL,\n    7104849328589122117ULL,\n    18369221892485385040ULL,\n    9571432625200090895ULL,\n    10535965759761355206ULL,\n    9665537130075426856ULL,\n    638897479820092399ULL,\n    12983706327448916459ULL,\n    14778716265260637324ULL,\n    11956873822209916526ULL,\n    13065206822551641159ULL,\n    5491159551080240894ULL,\n    13997616488141505830ULL,\n    16702756346708898139ULL,\n    1867885498002502378ULL,\n    13370540394532730625ULL,\n    11937737989641567504ULL,\n    3526631081139064372ULL,\n    10819826954173354016ULL,\n    5853110784898773696ULL,\n    2516611224077149082ULL,\n    114795980703283674ULL,\n    12005404529790652727ULL,\n    4171348542926422925ULL,\n    7703916159985659066ULL,\n    16823633804314484449ULL,\n    11086174287032770643ULL,\n    3793883246011577017ULL,\n    4819515149424157896ULL,\n    11928898683278395009ULL,\n    1568936456297772289ULL,\n    4735887039752312668ULL,\n    928744353812847374ULL,\n    7344657760999837484ULL,\n    9654120138010048629ULL,\n    13757300835340009887ULL,\n    11089494060458697445ULL,\n    12553853654406470896ULL,\n    15635891577654601472ULL,\n    8832028658029497452ULL,\n    8205588355149874384ULL,\n    13342439941530128638ULL,\n    12187280490095261703ULL,\n    8067383714071535925ULL,\n    16912593934876692659ULL,\n    12422644831702678416ULL,\n    18166397544234961082ULL,\n    2369882245955732194ULL,\n    11796726989164444146ULL,\n    17378717075346097540ULL,\n    3525883291478512256ULL,\n    848367955390211596ULL,\n    2006459348208063059ULL,\n    16343958618294069305ULL,\n    2863823323437208997ULL,\n    18251696668108646564ULL,\n    7337996130014577039ULL,\n    11892240709914127526ULL,\n    7495221918696414431ULL,\n    303113381492728210ULL,\n    15233237385859756314ULL,\n    12685319337324069157ULL,\n    12493636589782781733ULL,\n    1775301817975206056ULL,\n    13508307245017014997ULL,\n    3485527330376610512ULL,\n    4162379715913570674ULL,\n    16017879055605648063ULL,\n    13172744848535059982ULL,\n    15602355459393925084ULL,\n    15012633456961780659ULL,\n    10376254439602112165ULL,\n    15385597517216944899ULL,\n    2814637485477153397ULL,\n    9198964485540049496ULL,\n    6676333337772334082ULL,\n    5297159505961780773ULL,\n    17530653359255725303ULL,\n    8695324981480619758ULL,\n    13587411812706250597ULL,\n    10261506773264614466ULL,\n    14050039474732441752ULL,\n    8056669683552925742ULL,\n    1168931705088860849ULL,\n    10993595581236313885ULL,\n    3065620036691951867ULL,\n    10128519537204251112ULL,\n    1838506088752454580ULL,\n    4835256023845164616ULL,\n    709651759689231731ULL,\n    1525065980259613298ULL,\n    79959291357791363ULL,\n    9208054580783979299ULL,\n    13902083415557906632ULL,\n    3473093760207127230ULL,\n    16203495575669269215ULL,\n    12779298991881188818ULL,\n    7272541615641733654ULL,\n    15345744604780547172ULL,\n    13095756032997687824ULL,\n    2204833361668625529ULL,\n    10724173582475816957ULL,\n    15545048778036038900ULL,\n    9978794013812654518ULL,\n    856818290265354444ULL,\n    2889506655662089425ULL,\n    14115296302232803296ULL,\n    7563196760068723263ULL,\n    8485385394068311144ULL,\n    12049485316948913650ULL,\n    5663655475981455388ULL,\n    16537944248429051871ULL,\n    17781892274573073152ULL,\n    13494987947427383778ULL,\n    5716962659307098881ULL,\n    4275442129747448757ULL,\n    18135442194652572907ULL,\n    8900801549027438691ULL,\n    11963114502409307570ULL,\n    10062142025419647432ULL,\n    5895652008077875549ULL,\n    4631335624881002097ULL,\n    4662542499424321865ULL,\n    3358757962956692588ULL,\n    15114372007040201893ULL,\n    4440706939232180849ULL,\n    17995021598399256656ULL,\n    8127498309758732261ULL,\n    521151505841430304ULL,\n    1396322723480197038ULL,\n    11035329645669405883ULL,\n    1757815837875818655ULL,\n    8873490651245396144ULL,\n    10483150650916546522ULL,\n    4274864912807753567ULL,\n    5869008965405724563ULL,\n    7792416540993860030ULL,\n    14931034717213510044ULL,\n    9484948581386755200ULL,\n    5761228058690313011ULL,\n    9284504778112732092ULL,\n    6633585134868883126ULL,\n    12292738352984717287ULL,\n    10902622584257963897ULL,\n    628681741957641146ULL,\n    12079448067121759617ULL,\n    10760656019193445306ULL,\n    3672778528153768138ULL,\n    3390670181688366098ULL,\n    13267165257124597242ULL,\n    10270373981357709347ULL,\n    7301519417989144198ULL,\n    13989241316996978224ULL,\n    13426931212772572842ULL,\n    6444751773467579067ULL,\n    14940068127089162330ULL,\n    13319217303702988621ULL,\n    3363734170122156520ULL,\n    2580685914354674873ULL,\n    12722851617482606977ULL,\n    4906193330347601496ULL,\n    13111870407175695021ULL,\n    13743530607964923338ULL,\n    18123043232206904816ULL,\n    12225280854555501044ULL,\n    17007990560047949591ULL,\n    2698592928603177737ULL,\n    12191182564275140072ULL,\n    12208454231373372199ULL,\n    9830245398415199299ULL,\n    5424160629629644789ULL,\n    13968078082672584669ULL,\n    5863825222949994539ULL,\n    8648308022916788253ULL,\n    7240357069941765424ULL,\n    5946605689652378135ULL,\n    17017535000047952169ULL,\n    5171870438038426178ULL,\n    1244876970046738876ULL,\n    8465660519601025778ULL,\n    5504548963550724854ULL,\n    7771407730898950664ULL,\n    16368771188259720784ULL,\n    4311239670696750755ULL,\n    4968452184205841963ULL,\n    697097814947072955ULL,\n    11013488083031902958ULL,\n    14199829919664509577ULL,\n    7743310017525777353ULL,\n    854956437112527516ULL,\n    15784006904119809858ULL,\n    4798882063829361007ULL,\n    5617703021199534281ULL,\n    1197252000346614381ULL,\n    15060016762042652340ULL,\n    2081609199332392174ULL,\n    1675129038765752358ULL,\n    12332199727301977815ULL,\n    6125661215039136734ULL,\n    15672317145370182419ULL,\n    18221381779501877352ULL,\n    7228787152931836228ULL,\n    12735568401120880983ULL,\n    9233387875830017939ULL,\n    9143219540714851293ULL,\n    16714296060812624674ULL,\n    18201253524629860419ULL,\n    5446984105259166320ULL,\n    7937473934999406053ULL,\n    5910376975385683194ULL,\n    13154576458139876148ULL,\n    5420762873134539363ULL,\n    10365906532460919927ULL,\n    1673017889298233417ULL,\n    11552802984945530037ULL,\n    14814446083309710768ULL,\n    12779307538890327570ULL,\n    15079267257140745086ULL,\n    12203830292074701041ULL,\n    16933848102201662751ULL,\n    2045483273445943957ULL,\n    5159650813021108915ULL,\n    2856716663410507935ULL,\n    11073016866987985452ULL,\n    8915479933644108909ULL,\n    10012507498473792534ULL,\n    14998696283275266105ULL,\n    5112106394122086351ULL,\n    9214155745948408280ULL,\n    15412931815166359063ULL,\n    10222794371068498411ULL,\n    9821622687184294283ULL,\n    3431937847552504935ULL,\n    5662639919797214153ULL,\n    17885504976659472267ULL,\n    6591673573599737900ULL,\n    2168332678473414023ULL,\n    5214514320274186652ULL,\n    15038547246044350722ULL,\n    13345985432557011543ULL,\n    7209320308531573075ULL,\n    18220333491451921408ULL,\n    5678897429212405700ULL,\n    16064600085814907793ULL,\n    3472546797620339007ULL,\n    15340983014796521250ULL,\n    17186424670820756799ULL,\n    8026695432293249880ULL,\n    15245922278866504420ULL,\n    2942701409123021830ULL,\n    61685656663478174ULL,\n    17744502409497832192ULL,\n    597218275677198665ULL,\n    12070732252857882104ULL,\n    12577607755993540347ULL,\n    14577851801366563546ULL,\n    569424962551762664ULL,\n    4819743411046739820ULL,\n    18033373234276007025ULL,\n    3900231270563096763ULL,\n    18237347144771987933ULL,\n    1189635854127777866ULL,\n    11445790009393598516ULL,\n    13213457517821880611ULL,\n    8158543456630727814ULL,\n    14227771364659835707ULL,\n    6175861229769239237ULL,\n    2821859239710330492ULL,\n    18165223348748145981ULL,\n    7879500160558649018ULL,\n    9669144783373179736ULL,\n    9653381490544416935ULL,\n    1790491185694037868ULL,\n    13948351776143190799ULL,\n    5059832060453959225ULL,\n    5641623213857577086ULL,\n    8531873957825661008ULL,\n    8449674017136423031ULL,\n    16010761086236204726ULL,\n    8223340351077020328ULL,\n    2502506861199528149ULL,\n    12140564918112154349ULL,\n    12530299546340185083ULL,\n    9887057189834774931ULL,\n    9887057189834774931ULL,\n    8576898839131461099ULL,\n    14245745346864146954ULL,\n    16425537936422063455ULL,\n    6784902161520198595ULL,\n    16642584420979243102ULL,\n    10780548309269621176ULL,\n    2605618666653483974ULL,\n    14460164355407180547ULL,\n    14098427466058439125ULL,\n    5994098872828070339ULL,\n    14466607442696957193ULL,\n    14072559801773610090ULL,\n    5099310622143272229ULL,\n    4060612237789893594ULL,\n    12507027238332520124ULL,\n    15761310942686801560ULL,\n    866436462325504289ULL,\n    3869200021415839950ULL,\n    10640631636436308825ULL,\n    10432852934073152401ULL,\n    15657221014909940938ULL,\n    11036282581185334579ULL,\n    14746471652276158792ULL,\n    3184838388564580679ULL,\n    5693069587398182779ULL,\n    341485645424976849ULL,\n    11145543909036989457ULL,\n    15269501220065861110ULL,\n    15995184087178920289ULL,\n    10716423334959482557ULL,\n    2377248888797091206ULL,\n    4115822570748708776ULL,\n    11388933796231570976ULL,\n    14497192719562039543ULL,\n    11586395822997028015ULL,\n    4645773340435018756ULL,\n    118476643439542059ULL,\n    11169704403807182154ULL,\n    2219774214832864320ULL,\n    6089363088788742567ULL,\n    12348810704711838465ULL,\n    179945416471397200ULL,\n    18072226658460580274ULL,\n    8641145848019948113ULL,\n    17075842764203528331ULL,\n    16724898342327851092ULL,\n    8684178675525421804ULL,\n    3391624620423236573ULL,\n    6975778023245020533ULL,\n    15744887874383809519ULL,\n    9127441870184072352ULL,\n    13732241678392867004ULL,\n    15874528746776236542ULL,\n    11308136038699409267ULL,\n    10090462849616997114ULL,\n    2319851642455852322ULL,\n    14980878792580915251ULL,\n    7021852729868595658ULL,\n    11829638469203602971ULL,\n    5961000722500142009ULL,\n    2010988334373851604ULL,\n    8397343067170144572ULL,\n    7329340908130540942ULL,\n    11469793625926110448ULL,\n    8341214052999351393ULL,\n    11941850039160720038ULL,\n    2739517693202722669ULL,\n    454960022759158248ULL,\n    1869584801156740282ULL,\n    10520713985714859337ULL,\n    3374911733708483236ULL,\n    9946506611103202700ULL,\n    11223993919054829192ULL,\n    18309285555084955502ULL,\n    16890327522252192792ULL,\n    4310405874941698297ULL,\n    8033116960703072316ULL,\n    16071981091907285075ULL,\n    13597417902808862481ULL,\n    5671918273738509734ULL,\n    1231379187865589624ULL,\n    5743819681686072484ULL,\n    17303585594429125845ULL,\n    643586888697107538ULL,\n    10623850330738770572ULL,\n    8350128881660137785ULL,\n    7879471671503197828ULL,\n    14965300806909434771ULL,\n    9825797398655493928ULL,\n    15829502716039044256ULL,\n    6141126275953976273ULL,\n    13913899515411053537ULL,\n    2998965223158835912ULL,\n    867297293931461982ULL,\n    7345116506437505409ULL,\n    17338603876067779656ULL,\n    9545367699877783189ULL,\n    1834692154111193299ULL,\n    14830627386028111128ULL,\n    11672860550361259278ULL,\n    10414143939848848503ULL,\n    11326523905978997836ULL,\n    12308150692193025732ULL,\n    3846027965814650051ULL,\n    17669158754023571203ULL,\n    12188325058436471821ULL,\n    893038791899433117ULL,\n    7528986555014441259ULL,\n    9355881475747350793ULL,\n    9506589357362851429ULL,\n    8473635810382738528ULL,\n    16756411262318116093ULL,\n    2570665089862783418ULL,\n    6453035060108754733ULL,\n    12257615611857859109ULL,\n    8323164577417832502ULL,\n    7175958230147046152ULL,\n    4430523301164828322ULL,\n    9251000329672196383ULL,\n    4990427710551625836ULL,\n    7468899768688433647ULL,\n    9726348972748312352ULL,\n    11409693337251608260ULL,\n    1615050364109362318ULL,\n    16829685849726650205ULL,\n    10380730648414344921ULL,\n    7107188244499802138ULL,\n    11266727273456089960ULL,\n    9439342345611589587ULL,\n    9918092791983423099ULL,\n    4581276738904665928ULL,\n    8025116480227746022ULL,\n    11543416585745112172ULL,\n    17407131100665684792ULL,\n    1086450915778578322ULL,\n    1831083372562693809ULL,\n    6374231527496226540ULL,\n    8015367347074421025ULL,\n    11318676341435951810ULL,\n    1327912270717056111ULL,\n    3427703668299119184ULL,\n    11717680167065794140ULL,\n    13048788781534374626ULL,\n    12128317082618557090ULL,\n    17898894568269986959ULL,\n    6274020245741659168ULL,\n    14552047339087075988ULL,\n    3383676660014058912ULL,\n    818734912428938329ULL,\n    8438786199576916766ULL,\n    773719639023551534ULL,\n    12772172244193724329ULL,\n    5034732825400079180ULL,\n    7941179100324463905ULL,\n    13811086193082914122ULL,\n    6720398906925219186ULL,\n    10699697447945300176ULL,\n    11624306108012394100ULL,\n    17853630840698813664ULL,\n    4082018903322704577ULL,\n    1962091557509014189ULL,\n    17785331436284546685ULL,\n    4667598548862178845ULL,\n    6618069832304396ULL,\n    15510494483861912311ULL,\n    16655903700684371984ULL,\n    16219971186266781498ULL,\n    9648992599622936289ULL,\n    7877848085047343816ULL,\n    15934551949561042273ULL,\n    18439495745783061843ULL,\n    3286952060071349871ULL,\n    11729165847586466942ULL,\n    14927044058593075086ULL,\n    17972591775837562688ULL,\n    14050561518456598138ULL,\n    13704317380322751929ULL,\n    4205798040017274091ULL,\n    18015393910947568342ULL,\n    3413282044258557643ULL,\n    10507383618343192046ULL,\n    7148418804566291123ULL,\n    973175939423853560ULL,\n    16222653260529178807ULL,\n    15307964309993596677ULL,\n    11079658243751797796ULL,\n    996882297813599117ULL,\n    2848960757027274475ULL,\n    6927455324226944384ULL,\n    1426971738285605416ULL,\n    7055293552933584992ULL,\n    5433510150290408553ULL,\n    9497394168076074527ULL,\n    6586281728537107526ULL,\n    3866553109934735247ULL,\n    8691717521835605621ULL,\n    11075655361701695647ULL,\n    15566952123269095969ULL,\n    10636486022052414762ULL,\n    1228972866609697089ULL,\n    6720131548633544224ULL,\n    8277451931616866779ULL,\n    17465358754620213187ULL,\n    7511308886832264185ULL,\n    14871329507968214110ULL,\n    12792949510202019135ULL,\n    11280118962957614679ULL,\n    3086653447632689460ULL,\n    5142889603054261352ULL,\n    4776991110931092089ULL,\n    5017470542735072645ULL,\n    3524090706009551786ULL,\n    11169773346657361017ULL,\n    4585024966166591874ULL,\n    17962443748965456612ULL,\n    10427788546305418937ULL,\n    1178635746666671612ULL,\n    3340443756665183388ULL,\n    11250915178328546085ULL,\n    13904949387679567044ULL,\n    6214744891586462264ULL,\n    12471880659613319971ULL,\n    9530346073013573025ULL,\n    14969509193593753581ULL,\n    4273715943787407087ULL,\n    12243618151725562062ULL,\n    11708578121778514118ULL,\n    8678695290742706139ULL,\n    652416975719013268ULL,\n    17464592324648489788ULL,\n    11249897501343429325ULL,\n    633410116845312035ULL,\n    7469588151720230518ULL,\n    9577894317781215728ULL,\n    9828762314990147916ULL,\n    13618454942183172740ULL,\n    13880060555209828659ULL,\n    658605211171047744ULL,\n    12272027473061616277ULL,\n    4004438423025373610ULL,\n    2583940694094995381ULL,\n    13301601068322738776ULL,\n    4101279113912975284ULL,\n    5858235705055605228ULL,\n    5810307123235729470ULL,\n    13981560279594353615ULL,\n    1658473343027293991ULL,\n    13575145523497625729ULL,\n    12739106425376991277ULL,\n    2520404150970719859ULL,\n    9389038075179138300ULL,\n    6100334389452305792ULL,\n    9493222785580177571ULL,\n    14033966692292086166ULL,\n    15480900219633823043ULL,\n    10765629729262362021ULL,\n    13527312071547332153ULL,\n    12378956226374948863ULL,\n    17370734672278564863ULL,\n    2430127020953458880ULL,\n    12257903534775569134ULL,\n    1715111782699741031ULL,\n    6546772316297694437ULL,\n    6401732411202784160ULL,\n    4409252929806640757ULL,\n    9316007178466860786ULL,\n    3870405006267222384ULL,\n    14001717390532491989ULL,\n    10546997836312665781ULL,\n    14494491557005554488ULL,\n    11201832427405724532ULL,\n    14531847842993913464ULL,\n    15366873004491100313ULL,\n    2464180448062060661ULL,\n    544931618946096598ULL,\n    12111356697619274336ULL,\n    5818526526817004026ULL,\n    7956701155956054264ULL,\n    14130168630807296324ULL,\n    13692834942098558903ULL,\n    12146844769294605287ULL,\n    7990922245830817023ULL,\n    14774283556796669089ULL,\n    112272453166719332ULL,\n    12546566050809307190ULL,\n    10951599714615334764ULL,\n    12704995156168448459ULL,\n    10654816214017551205ULL,\n    536014619925651164ULL,\n    3354469534696244734ULL,\n    15668977646198108744ULL,\n    8618485860573818446ULL,\n    5082995861232064529ULL,\n    6145756778443652902ULL,\n    1483844006457656179ULL,\n    7791341198193559159ULL,\n    11407397596035416986ULL,\n    4890249398170314754ULL,\n    331970667432018520ULL,\n    17266470247160558799ULL,\n    9621539626217656319ULL,\n    9792494282348105232ULL,\n    16394701671512250680ULL,\n    2145903491145660241ULL,\n    14955368525306955393ULL,\n    8491874958854443255ULL,\n    9995604794334887028ULL,\n    11255835401816773528ULL,\n    17749905697160318183ULL,\n    336063646905865685ULL,\n    6186476254259830693ULL,\n    11443143299919207815ULL,\n    18202722262437729896ULL,\n    4552367301005432295ULL,\n    5625666157252064939ULL,\n    14204681731479024893ULL,\n    14819700369052386237ULL,\n    7605991604504873406ULL,\n    13319014080937881793ULL,\n    4228424950655384741ULL,\n    3136715284561859667ULL,\n    3797177139495693505ULL,\n    10706279149092517688ULL,\n    11413529812019457957ULL,\n    4006691196168425106ULL,\n    7418465187222617222ULL,\n    14704293106269088426ULL,\n    17595497157843007650ULL,\n    6491545582682523999ULL,\n    15862971336239284141ULL,\n    5686701677206427640ULL,\n    16451400838894059983ULL,\n    4358443752841640468ULL,\n    18423702616091319800ULL,\n    2111043077325460789ULL,\n    1981023287553424020ULL,\n    8254310130471253356ULL,\n    17200366928353365217ULL,\n    4572372321949679931ULL,\n    18288040824798672686ULL,\n    10240149940309224065ULL,\n    8610113896360102336ULL,\n    18206306752130904718ULL,\n    16051245626762498885ULL,\n    14420133578374234637ULL,\n    17630103502996531786ULL,\n    1485320106593048371ULL,\n    7110289158842245424ULL,\n    3427283450482136586ULL,\n    12464613933882266170ULL,\n    4094424101916028755ULL,\n    11284847456387790116ULL,\n    11368649573580391609ULL,\n    16524469873627385466ULL,\n    4525522258127830444ULL,\n    10692687498919892110ULL,\n    11186257673053233851ULL,\n    4404994858146333309ULL,\n    18044346897038469332ULL,\n    9998997686460801540ULL,\n    3282340509227031838ULL,\n    14190018384083470688ULL,\n    18067753429254816290ULL,\n    550661846429631190ULL,\n    4282408549975338757ULL,\n    3614900342801985344ULL,\n    17695327435529983701ULL,\n    10893533162120630793ULL,\n    13934767290623498830ULL,\n    18032453925890744399ULL,\n    671649577702987885ULL,\n    5695524958235453738ULL,\n    17290853643570518204ULL,\n    10147804298505476892ULL,\n    10865166598773235811ULL,\n    1407818763313326082ULL,\n    5936544796019507132ULL,\n    11858981454634938040ULL,\n    6835826908768355906ULL,\n    14567325364718250102ULL,\n    13374905634998037034ULL,\n    15820991821047927904ULL,\n    10119657062856748091ULL,\n    896189263449679402ULL,\n    2607620399263787626ULL,\n    12728675172521683130ULL,\n    9660451360752769902ULL,\n    10345320277567462847ULL,\n    8704997179690474560ULL,\n    15964890310631413592ULL,\n    6351819485349166065ULL,\n    10724537413501995097ULL,\n    1371673613305652334ULL,\n    3736585133788975247ULL,\n    10554538713530489561ULL,\n    15312037039014010129ULL,\n    6956413110099818193ULL,\n    18425424462606621780ULL,\n    13088758916340939792ULL,\n    5414086710836912196ULL,\n    2997154599580423181ULL,\n    11696455433479581695ULL,\n    6511721942536088768ULL,\n    3950125865196258054ULL,\n    4545658813820002532ULL,\n    509576450901606649ULL,\n    16927170973212935628ULL,\n    16578936878150284598ULL,\n    17195465731838580837ULL,\n    14418010845088262072ULL,\n    5684073397697328286ULL,\n    8143806543198754783ULL,\n    9361980519305459491ULL,\n    4324358879721591088ULL,\n    3377732966265486593ULL,\n    1603577653080741006ULL,\n    7778408070497092058ULL,\n    6795129235518226470ULL,\n    8198674595486293592ULL,\n    17308605419676257118ULL,\n    2516532493498065940ULL,\n    7515435695903893835ULL,\n    9362532201570799100ULL,\n    17000540944471452772ULL,\n    10686709376088632546ULL,\n    13962167458447908589ULL,\n    10534208516011028508ULL,\n    7527043027894172126ULL,\n    6205935423926046869ULL,\n    16221877186684330173ULL,\n    15600326551271913607ULL,\n    14585053613104089414ULL,\n    7981421870017751373ULL,\n    11753160284862637027ULL,\n    466145054588804647ULL,\n    5958942503622532154ULL,\n    8277364052508189754ULL,\n    10640400737142300570ULL,\n    1128788486330077589ULL,\n    12008462896355002969ULL,\n    3526415014835764579ULL,\n    14343257517130816531ULL,\n    12450101410341107044ULL,\n    843936302021964092ULL,\n    2826974608677871878ULL,\n    5119464066529504593ULL,\n    12501148658143736328ULL,\n    9159630037941308309ULL,\n    8292567273955695489ULL,\n    1920179511660231237ULL,\n    13055377622122336743ULL,\n    10424628371875203728ULL,\n    15800917410170716754ULL,\n    3803447690489063226ULL,\n    10733324093560261769ULL,\n    18343518969143062959ULL,\n    14778851814209208712ULL,\n    5606702454821688119ULL,\n    4433512416242729291ULL,\n    8226138258465920396ULL,\n    17351692808279922968ULL,\n    15790056605615294463ULL,\n    5058549875635716964ULL,\n    3409317061396743231ULL,\n    18174253588988753504ULL,\n    15672784223066734574ULL,\n    4474100889019566559ULL,\n    3438883945466823858ULL,\n    3219216156442985827ULL,\n    15260357126503000657ULL,\n    10622153912646372469ULL,\n    1909262261092377153ULL,\n    7730381237907164144ULL,\n    8784065456410604338ULL,\n    14530832482079541373ULL,\n    6173109868976300355ULL,\n    9930398822244399216ULL,\n    1773677737031781992ULL,\n    16980276403124952020ULL,\n    18170375288487624349ULL,\n    4569683471986031824ULL,\n    7541141564505614892ULL,\n    1895576511575229993ULL,\n    7755118074503646575ULL,\n    7091481092936535777ULL,\n    17596520681879371953ULL,\n    6262048760649076480ULL,\n    1838600894017113801ULL,\n    16814836061718227666ULL,\n    11412485070438171164ULL,\n    17241713446250323161ULL,\n    5162678431797942829ULL,\n    14737152370914675182ULL,\n    825278782297693329ULL,\n    17241369539689250764ULL,\n    15395157532314233105ULL,\n    13693183289866598504ULL,\n    3865750470216266483ULL,\n    7846858360249643730ULL,\n    3569552894303832286ULL,\n    14028222716931337068ULL,\n    10650279896189401070ULL,\n    10559074586139607784ULL,\n    17974736064430080336ULL,\n    4131226028475820878ULL,\n    8947717113384599004ULL,\n    16448156465905474500ULL,\n    13704678662563690113ULL,\n    12644623095651391718ULL,\n    13288724719926100172ULL,\n    17317427275883751814ULL,\n    9340347374406538135ULL,\n    3636560584197667677ULL,\n    6441255017632990032ULL,\n    4030982113414691532ULL,\n    7807925011210604955ULL,\n    17142182496418950968ULL,\n    8413121888185890327ULL,\n    6179772856778125411ULL,\n    12068644354382374423ULL,\n    9436517725675296122ULL,\n    7232779122205205600ULL,\n    16384826609439231134ULL,\n    6862587354124881055ULL,\n    17248189511999965576ULL,\n    9038586555751106685ULL,\n    11095410145471697175ULL,\n    3757019282247714034ULL,\n    15537438349047528280ULL,\n    8455215476688568241ULL,\n    15685547440435771800ULL,\n    1709564835245558037ULL,\n    12948099081465166360ULL,\n    450146941766423213ULL,\n    11191239560523454272ULL,\n    4012527811007011139ULL,\n    6883664299164076420ULL,\n    3826933378203077721ULL,\n    14903870087252246300ULL,\n    14646004384842439660ULL,\n    5552296166688992831ULL,\n    17020332566561430616ULL,\n    14141509291818875960ULL,\n    17766022269685018327ULL,\n    12368195267582794412ULL,\n    3039963620860976010ULL,\n    13258507746996622330ULL,\n    4669238426005475892ULL,\n    7306060018530684653ULL,\n    5199174751110718392ULL,\n    6921912246090863351ULL,\n    1152428639997461760ULL,\n    1302519311394873230ULL,\n    7178152480048959496ULL,\n    2835049175312588522ULL,\n    1412181455315810835ULL,\n    12811490626475109963ULL,\n    15929982858603090189ULL,\n    5820115615240011095ULL,\n    6595497363938321114ULL,\n    10358739874625168080ULL,\n    3636263903722622834ULL,\n    5962547455565718094ULL,\n    7386190469891254602ULL,\n    15805127161949359309ULL,\n    8433431915857022683ULL,\n    15035537266644739264ULL,\n    3197241151620903039ULL,\n    16257075086443022311ULL,\n    10209016152237336951ULL,\n    6019896668854652729ULL,\n    8682663237499947741ULL,\n    8140541025033425922ULL,\n    3550391931853265663ULL,\n    10284715978998345124ULL,\n    17963947675485481702ULL,\n    14534280173129152781ULL,\n    16693577308012502161ULL,\n    440889681933011737ULL,\n    7416042034543129513ULL,\n    14979185565200755138ULL,\n    9898002558289927259ULL,\n    17079293577965272709ULL,\n    15558802641753335682ULL,\n    7023806442758911380ULL,\n    8953986948621830234ULL,\n    12894556581002852953ULL,\n    3888376880486207209ULL,\n    16503946261518137638ULL,\n    8477669915822337849ULL,\n    17087382437264372622ULL,\n    17573031842761637666ULL,\n    11035010766913100585ULL,\n    8219838752355703070ULL,\n    7235764082868066099ULL,\n    14143235338987058395ULL,\n    9025634948083651267ULL,\n    17297264301099796863ULL,\n    939872652036263865ULL,\n    10656080303117348564ULL,\n    7244909541420153053ULL,\n    9261809231799071264ULL,\n    5724483147832484179ULL,\n    2804683954651583540ULL,\n    3321611163207228762ULL,\n    2528362023756991692ULL,\n    11323964568292407170ULL,\n    7834652255933920026ULL,\n    16561535212593086129ULL,\n    14896262807861051804ULL,\n    10944803353900409ULL,\n    1225204550703353447ULL,\n    3075075714978752699ULL,\n    17558925943832918803ULL,\n    12867035878395902451ULL,\n    872270544651312045ULL,\n    11469699280077079476ULL,\n    15729762875563896626ULL,\n    7611106953658649807ULL,\n    3002451008721207862ULL,\n    5120531053933761598ULL,\n    6680061477955888735ULL,\n    5942402361569153933ULL,\n    3713181907957169132ULL,\n    9441986749029681947ULL,\n    10384354634280891189ULL,\n    14493989841643005839ULL,\n    17527952055471175960ULL,\n    13252725631537091971ULL,\n    13061906945598100198ULL,\n    3027364250910709220ULL,\n    9052990702160304868ULL,\n    13495853800793214215ULL,\n    16820139676096707468ULL,\n    8168523847463272611ULL,\n    13439217052848144897ULL,\n    10874243802196693953ULL,\n    3210514065222361107ULL,\n    17971003154228766469ULL,\n    17331972856075808308ULL,\n    18314716318561559791ULL,\n    4182784058874514432ULL,\n    18108157827490612131ULL,\n    8162374656541055899ULL,\n    1942203428647001423ULL,\n    1117150270668713085ULL,\n    626129625423455898ULL,\n    9453924528068863726ULL,\n    17295492069127782219ULL,\n    631674933598335661ULL,\n    1994275795585646276ULL,\n    5271243145887833741ULL,\n    3135959296909108254ULL,\n    17245563097965681236ULL,\n    7898146824987997718ULL,\n    1027540800335677407ULL,\n    10462214274340632030ULL,\n    2122454490759770637ULL,\n    14549383453078055199ULL,\n    8232512930088741928ULL,\n    15937955492933015529ULL,\n    9537475499890880544ULL,\n    16382766086269057228ULL,\n    910846485791178492ULL,\n    519613352681206622ULL,\n    4724733717533965660ULL,\n    18059114523368415368ULL,\n    13594740066534274164ULL,\n    2568741305265272913ULL,\n    15124672811450907489ULL,\n    15021493339489975848ULL,\n    5874176286011044246ULL,\n    3175647371016669510ULL,\n    9448367578543144300ULL,\n    17129694252927994779ULL,\n    5892278191441573399ULL,\n    4430437819476828826ULL,\n    16810074949133001489ULL,\n    14054355089507067591ULL,\n    4551857525491309088ULL,\n    16055634188738499387ULL,\n    8563633767109800777ULL,\n    9699136173358685003ULL,\n    5123918841527167698ULL,\n    16802360079662288079ULL,\n    12593487153072667687ULL,\n    12946301027340997551ULL,\n    15837946286565318430ULL,\n    5403588250250896055ULL,\n    9459694303787363017ULL,\n    3358478116993469464ULL,\n    4285490664746366942ULL,\n    13405149029408027982ULL,\n    11409632446797510494ULL,\n    10744466254785294214ULL,\n    17261957640066765123ULL,\n    944385084571085435ULL,\n    220036017951850409ULL,\n    12976147158841848854ULL,\n    14364422219415352165ULL,\n    15320697515514904491ULL,\n    11993993499423060294ULL,\n    10355515460709079484ULL,\n    7157829100214703608ULL,\n    14788500291960541959ULL,\n    2789341736572050973ULL,\n    9311850079756139345ULL,\n    9656443454566582802ULL,\n    5815833967059108353ULL,\n    10693900934291851315ULL,\n    4713679297632201267ULL,\n    6609625825874948293ULL,\n    13203516971884436595ULL,\n    643707473696532104ULL,\n    15981367418022715758ULL,\n    1137486369224510782ULL,\n    12198813957918328720ULL,\n    5279204362755245792ULL,\n    6933263802168953407ULL,\n    15746618922991787657ULL,\n    15836056368761207809ULL,\n    8866698354905145390ULL,\n    484450052375510705ULL,\n    4212436985518828868ULL,\n    12430832846698594529ULL,\n    8076782890367591972ULL,\n    7356679031008276946ULL,\n    6895119212963555481ULL,\n    14185201922559143811ULL,\n    550261971434962957ULL,\n    14752454540301467218ULL,\n    11900046633828217319ULL,\n    321936324165106847ULL,\n    13668247969675834500ULL,\n    1290626341468016817ULL,\n    10736456731787344426ULL,\n    5252983960583781079ULL,\n    14658079907089113628ULL,\n    7947834161852096496ULL,\n    16870763013714596162ULL,\n    2349051861118713474ULL,\n    17059149602813520196ULL,\n    4864300222293336925ULL,\n    7433362783328356395ULL,\n    5469071435015178894ULL,\n    16017581063534926006ULL,\n    4604364674308206215ULL,\n    8201331706076723711ULL,\n    3531100377780061969ULL,\n    14724108889750360501ULL,\n    177456909693485922ULL,\n    4663892207528972926ULL,\n    3407552007794503941ULL,\n    2384013931246898089ULL,\n    3878613613323120222ULL,\n    2022805253174798688ULL,\n    11534472861098728552ULL,\n    3272540468735637191ULL,\n    6275494661211799920ULL,\n    8523421395712636429ULL,\n    4220085914733221290ULL,\n    9118945763666087117ULL,\n    15345091930373589697ULL,\n    17846019037822740894ULL,\n    6591315164212644903ULL,\n    17806011248621649027ULL,\n    11999779262780520708ULL,\n    5652279769315989702ULL,\n    8765608314908714337ULL,\n    8134523433789517916ULL,\n    5971325011761944660ULL,\n    783255320595442559ULL,\n    9294487786089885921ULL,\n    13457401299387278264ULL,\n    8942350674116757449ULL,\n    13840822753771821441ULL,\n    3408666551361846021ULL,\n    7393956174467040177ULL,\n    10646952746586228728ULL,\n    17327723350809252574ULL,\n    16817911986121729054ULL,\n    1761818441722922755ULL,\n    13459359673495951942ULL,\n    17986488659628096091ULL,\n    11060024588324920732ULL,\n    13428002658940814182ULL,\n    1975100947738652288ULL,\n    671868120376033371ULL,\n    13733214297214134872ULL,\n    9908024178551321937ULL,\n    17816908615398689935ULL,\n    771656266119076645ULL,\n    12755163178304627856ULL,\n    11456294863405181371ULL,\n    16619601815123735037ULL,\n    17847463976607896361ULL,\n    9575845900998981348ULL,\n    17348406868968743034ULL,\n    14853354703679684318ULL,\n    17945111410018173095ULL,\n    330428342314483794ULL,\n    11311033008219095348ULL,\n    16132683164238459935ULL,\n    16173625339831672205ULL,\n    15381881938105119676ULL,\n    1097804494645072932ULL,\n    9278533152166355237ULL,\n    823285273499912466ULL,\n    11770039515852000070ULL,\n    8749977948880856348ULL,\n    8626542913776197712ULL,\n    1460297196261522082ULL,\n    2094304584232850481ULL,\n    6653407231875155575ULL,\n    3525373909735944821ULL,\n    2600443145106784337ULL,\n    7320793908477554199ULL,\n    301701575925065996ULL,\n    745543312197207105ULL,\n    420911447232803308ULL,\n    14336499934097377759ULL,\n    3913956963508481669ULL,\n    745407682518258168ULL,\n    10352384243980296993ULL,\n    745490407954695631ULL,\n    3355761947268104794ULL,\n    5294842547950608627ULL,\n    3527725199244386496ULL,\n    16664797658482385127ULL,\n    2652883177032024747ULL,\n    5475319014259076162ULL,\n    3053477648250201605ULL,\n    9853055210099433364ULL,\n    12475376441331059463ULL,\n    12174330461148672467ULL,\n    7854842158084002686ULL,\n    10800575116344288972ULL,\n    2875600274112740582ULL,\n    11916702384786428367ULL,\n    8433017686587569272ULL,\n    4175045899043435627ULL,\n    13937188739816932436ULL,\n    10852744883153744444ULL,\n    16941152258468741967ULL,\n    14322768088693033479ULL,\n    12631775412788448684ULL,\n    11951178867618091078ULL,\n    8934159513753876999ULL,\n    11258705575809294038ULL,\n    3130315234516782917ULL,\n    7864935737567774256ULL,\n    17348033291747971722ULL,\n    7365939734320710779ULL,\n    8339910816380426005ULL,\n    4930167307673758531ULL,\n    10511754687554295879ULL,\n    15336397204080430279ULL,\n    1119903149739449558ULL,\n    10660433539573900631ULL,\n    14423641125531866739ULL,\n    5375059421230324249ULL,\n    17063742988348357819ULL,\n    10026353307080739845ULL,\n    9406364837098618719ULL,\n    8837170290669357763ULL,\n    16699762578717659673ULL,\n    7971097379228366708ULL,\n    7143776409284046343ULL,\n    15707090185530548878ULL,\n    5764720597421110412ULL,\n    6643362790097050510ULL,\n    17143096512245478437ULL,\n    15202930820687632629ULL,\n    17535222740160595333ULL,\n    9297958534747412201ULL,\n    18117709416883968764ULL,\n    4438216060430338415ULL,\n    11015192289722125940ULL,\n    12680684654794731643ULL,\n    6805126828956628329ULL,\n    4084081524595020093ULL,\n    3293402975373320580ULL,\n    12303067226856518882ULL,\n    3771035490119339463ULL,\n    8988270290616980723ULL,\n    4752189042272941044ULL,\n    6856963484602716662ULL,\n    14607948814267151307ULL,\n    14607767022065970563ULL,\n    2742229215736438548ULL,\n    10496192523608278911ULL,\n    2858969103666340497ULL,\n    13894594266928833ULL,\n    14375726796438156190ULL,\n    11884365082439900867ULL,\n    13723637642314502030ULL,\n    1884797662897622787ULL,\n    10861389792925708149ULL,\n    14790248857970306827ULL,\n    511450668237404954ULL,\n    7696828488816517710ULL,\n    6432846379594075600ULL,\n    11230815215463018403ULL,\n    12590332785905394854ULL,\n    10392922656449731948ULL,\n    15434173770801607632ULL,\n    9799936090244685098ULL,\n    8103223797988077604ULL,\n    15137307079479035703ULL,\n    660323882351530338ULL,\n    1157361097124774994ULL,\n    15497635176317074005ULL,\n    5648539157614273884ULL,\n    1089829165886876ULL,\n    8808911906626576089ULL,\n    4293387324748576011ULL,\n    17899849958089015544ULL,\n    10189954808783314051ULL,\n    11372782738083131713ULL,\n    1179546201345864571ULL,\n    4144379609522818236ULL,\n    12448942292023332565ULL,\n    3993465782121982310ULL,\n    3090096687640140487ULL,\n    172753067907364599ULL,\n    15079124169983684023ULL,\n    4373438032267680931ULL,\n    549361584461377963ULL,\n    711144378695554405ULL,\n    10496342186021555702ULL,\n    17800899327042417320ULL,\n    7053955819823240340ULL,\n    12493152739073667060ULL,\n    4363310736911234186ULL,\n    18103729674309456164ULL,\n    222688125525841996ULL,\n    9500915974263946146ULL,\n    8413867423194850139ULL,\n    10061425646773276557ULL,\n    16676709090730842448ULL,\n    17441308824435700088ULL,\n    12752485820190581797ULL,\n    9672562476976598030ULL,\n    8380542434060856678ULL,\n    3799362629100875531ULL,\n    13789860209234596643ULL,\n    8810608937111777145ULL,\n    4371183765075736878ULL,\n    7642634076981280029ULL,\n    16040466036366531043ULL,\n    9977808804959486824ULL,\n    1777237608932529089ULL,\n    11304646699951898967ULL,\n    9673698068476408770ULL,\n    4513307592611757218ULL,\n    3613237438230569561ULL,\n    17544039117194461089ULL,\n    10138784223575069809ULL,\n    11768539004389552153ULL,\n    11286580857620103213ULL,\n    16161162816718645517ULL,\n    1998870209801941888ULL,\n    13575190491193438247ULL,\n    156363902583001517ULL,\n    10606832843353940515ULL,\n    17985642152316190860ULL,\n    10132628141316581592ULL,\n    603379928681765921ULL,\n    17954220654197053261ULL,\n    2546958370527126728ULL,\n    6084781478758229395ULL,\n    14801605266656615796ULL,\n    7223845733417339051ULL,\n    16309168703639808991ULL,\n    11082962024502751355ULL,\n    5020483032044909035ULL,\n    17815434753789126799ULL,\n    1608684844785524562ULL,\n    17771860346162534519ULL,\n    10472414625059677697ULL,\n    18210021813704633127ULL,\n    3012656419023249462ULL,\n    13668829787011233196ULL,\n    2020702208507862494ULL,\n    10527869418330528983ULL,\n    11879335070900696645ULL,\n    14804900977943907825ULL,\n    13891769002811353400ULL,\n    3082413377954335347ULL,\n    3565790902873435350ULL,\n    6839030778310196105ULL,\n    14136096743024225815ULL,\n    13189670765211641801ULL,\n    7814464595175079987ULL,\n    12008548288484372041ULL,\n    5901595585890637996ULL,\n    8315387223931653823ULL,\n    2224664174920727015ULL,\n    3512017407929714369ULL,\n    5581640832285626197ULL,\n    14773509103905582880ULL,\n    12165022583821241131ULL,\n    1375760756928870103ULL,\n    17799105989092486245ULL,\n    14801916093953046615ULL,\n    13907542387479148269ULL,\n    10049421225958724322ULL,\n    15699934033142499107ULL,\n    14597389651444034348ULL,\n    17213092310918722160ULL,\n    15067361549933483843ULL,\n    8365668772026089315ULL,\n    17282415885004899081ULL,\n    973463241254481825ULL,\n    17425373904384597240ULL,\n    373147832092038175ULL,\n    1050089252447843111ULL,\n    5886920084292739291ULL,\n    11895807850556184493ULL,\n    4508791570152452759ULL,\n    15978377047622173650ULL,\n    4709875329972269265ULL,\n    14548148827608787588ULL,\n    16449714669840779748ULL,\n    14043948894944207747ULL,\n    5708795488973392011ULL,\n    2314745240124573747ULL,\n    16874659415509035175ULL,\n    14912665938432495273ULL,\n    5216136634449106838ULL,\n    6621056630695433914ULL,\n    10617912609699043100ULL,\n    9358464794394338764ULL,\n    9416592372638622013ULL,\n    226672593314459883ULL,\n    6088611467335848086ULL,\n    3258959012672982570ULL,\n    9158594969039092683ULL,\n    1561972579213582565ULL,\n    742575536972916868ULL,\n    1078334539465153560ULL,\n    18045884535985695367ULL,\n    13507291141326245278ULL,\n    9343148952661184646ULL,\n    17947293398819285412ULL,\n    4679654437312032469ULL,\n    8361868859443437047ULL,\n    2651585630683768203ULL,\n    11044570696105880806ULL,\n    3252279415460060836ULL,\n    17777271761739318070ULL,\n    15209528820715002165ULL,\n    9068149427388885283ULL,\n    6621835438206313999ULL,\n    5078998223004783887ULL,\n    3329405504089107094ULL,\n    13449424956278447347ULL,\n    14462273558578476930ULL,\n    11791013456615552172ULL,\n    16901353769665173023ULL,\n    5889154489750180004ULL,\n    9683501360203314571ULL,\n    5118868590944775088ULL,\n    4302730910073269131ULL,\n    5244410234705221464ULL,\n    16941818708819735006ULL,\n    17249169388152853794ULL,\n    17818814984499794561ULL,\n    11935399269618954638ULL,\n    13912079852425082769ULL,\n    7449285039010691905ULL,\n    13272733936669852823ULL,\n    13077154112711074903ULL,\n    2544476422635720571ULL,\n    9917622555771523735ULL,\n    1401373283955211150ULL,\n    4131387292353983474ULL,\n    9221319519442322619ULL,\n    10271096730818380987ULL,\n    4349348541264102181ULL,\n    15608920019685690241ULL,\n    13870050185713501452ULL,\n    11890374698958552516ULL,\n    15349447739813429213ULL,\n    17437619774274474126ULL,\n    12248299835922725678ULL,\n    5291445165740621936ULL,\n    8038671103241778789ULL,\n    12060028942846433639ULL,\n    4509233015459420475ULL,\n    643180764943347744ULL,\n    994157228082765472ULL,\n    10887607236534736558ULL,\n    934798733919360659ULL,\n    3368165583255026370ULL,\n    2076839466195007405ULL,\n    13443469130031139409ULL,\n    8367682099489166253ULL,\n    2125674162751811223ULL,\n    9990807060880866882ULL,\n    17394088508122231814ULL,\n    11757529717494926914ULL,\n    3187824009385859411ULL,\n    10966777805957954623ULL,\n    3870341214616800184ULL,\n    6023551681934394607ULL,\n    17589502580855815210ULL,\n    7172648547929977317ULL,\n    4058349330184047178ULL,\n    2211901395395798706ULL,\n    12743774863108918303ULL,\n    15320135379886977789ULL,\n    17147637292945212545ULL,\n    13987566276345734720ULL,\n    4067023674166500722ULL,\n    5870180151344150815ULL,\n    11932062297827142492ULL,\n    12950434668599572809ULL,\n    16819056124206693395ULL,\n    3291855202668117537ULL,\n    7757976903511781626ULL,\n    122107839697862470ULL,\n    3413142924802729464ULL,\n    7454187765893598468ULL,\n    7968503300258229110ULL,\n    5470183611835298691ULL,\n    14474864210835245721ULL,\n    3954356648594381876ULL,\n    1203576788389720011ULL,\n    13602180523878073346ULL,\n    6716116329796267946ULL,\n    6590373262065052569ULL,\n    17274595806792552259ULL,\n    15333358509884607467ULL,\n    4226869313685499837ULL,\n    18150876205965279116ULL,\n    7470217877614645414ULL,\n    16449603603895044121ULL,\n    5671080049107181205ULL,\n    1445351349851319119ULL,\n    9864740424984853830ULL,\n    9827706874003814974ULL,\n    14837280824310196055ULL,\n    6560851287923595953ULL,\n    9836472848208188013ULL,\n    14691829054664810672ULL,\n    1336561861487759111ULL,\n    8661852267849592999ULL,\n    4579961346361874892ULL,\n    17452937695690152372ULL,\n    2407685403987878928ULL,\n    8940878107572396130ULL,\n    14493870008348064143ULL,\n    12117015166546605096ULL,\n    15458201882160254741ULL,\n    12479563054900113679ULL,\n    4997537931085004093ULL,\n    6456054670061613603ULL,\n    4596074859203471785ULL,\n    2247202908835719559ULL,\n    11790179722589051907ULL,\n    2802846863328190045ULL,\n    6175668446932214881ULL,\n    15388134621795555068ULL,\n    15841825884072675653ULL,\n    2768170468407956284ULL,\n    8934197641561484762ULL,\n    16097445743471219327ULL,\n    17753041514565282075ULL,\n    14425815027897914658ULL,\n    10827915952169273519ULL,\n    17280291941746394173ULL,\n    8187451720382932139ULL,\n    141441271333998660ULL,\n    17893869509508841966ULL,\n    10581348484784225885ULL,\n    17762963401331238101ULL,\n    2167293615188131181ULL,\n    2200050483577847924ULL,\n    12988156401759680354ULL,\n    13245035733164596392ULL,\n    1548372814394988466ULL,\n    5378096591718715433ULL,\n    2074912868947358658ULL,\n    8964923667179207153ULL,\n    1366756423179302770ULL,\n    16567809928206146636ULL,\n    15419232348049359544ULL,\n    10652520587055804607ULL,\n    15493163217003127304ULL,\n    2207040903029103652ULL,\n    16363222667994704889ULL,\n    12742577209190520233ULL,\n    12390124032809051068ULL,\n    5875298930604826007ULL,\n    4159950081840610345ULL,\n    1942550469455131123ULL,\n    6120084820195929489ULL,\n    4189359863922943742ULL,\n    8316221204566775255ULL,\n    3406448494150058290ULL,\n    14977527868723308074ULL,\n    3771350577658747951ULL,\n    2816011047888933129ULL,\n    2395895766440909881ULL,\n    5112064748503861655ULL,\n    16414337328521294985ULL,\n    1708599863801602850ULL,\n    3663957190589297758ULL,\n    4957488558324390979ULL,\n    13144279342191749006ULL,\n    6813923573274028356ULL,\n    3516089207819659534ULL,\n    8495936761223186148ULL,\n    14942103494662035456ULL,\n    8696656931805617185ULL,\n    5710302710393869967ULL,\n    14232375125546869355ULL,\n    11703120560058190753ULL,\n    8369891784536285845ULL,\n    12449169751035566267ULL,\n    6289690297444355826ULL,\n    14265367171767269436ULL,\n    10531605808489440433ULL,\n    2854253393116336280ULL,\n    17539528451750896645ULL,\n    14989146486837134234ULL,\n    12788087835298615807ULL,\n    9401614376319490239ULL,\n    17989882563198331548ULL,\n    7642760839170341120ULL,\n    4563983619718351671ULL,\n    13310369065591483047ULL,\n    11985461715745397294ULL,\n    13679706540415479286ULL,\n    1125513908676837562ULL,\n    314994494561372920ULL,\n    15796679806720198361ULL,\n    10625119312901365711ULL,\n    3332434413502694470ULL,\n    1797260467503516716ULL,\n    793899453678181398ULL,\n    11087375562591858754ULL,\n    14725373244267567835ULL,\n    10253358618372865718ULL,\n    5185727477821764106ULL,\n    5388917257996973734ULL,\n    2303924165488920218ULL,\n    9619103795628174450ULL,\n    16674248551048994212ULL,\n    3559417062803223541ULL,\n    4467082467385230037ULL,\n    6874564979062261936ULL,\n    14782093411657118117ULL,\n    18232166913550761464ULL,\n    4807293091714203147ULL,\n    13573404852514895082ULL,\n    4734366808752906418ULL,\n    11353370389311699377ULL,\n    1875048800876749307ULL,\n    12603169512723050616ULL,\n    11512882478911021099ULL,\n    2809329710034347897ULL,\n    17017690132360359500ULL,\n    12957025922505133147ULL,\n    6699220427367202732ULL,\n    15832783117361669028ULL,\n    10619449008272680199ULL,\n    17023477352262652510ULL,\n    5034117659648645291ULL,\n    8879195316242279251ULL,\n    10014006411953872938ULL,\n    9841368135266036811ULL,\n    953437937678241701ULL,\n    7916173360992510373ULL,\n    16183106400064249147ULL,\n    5061118182006080292ULL,\n    18272533593429154668ULL,\n    1902179109959303086ULL,\n    15224583361250793333ULL,\n    1377240555995868512ULL,\n    11290002100751824090ULL,\n    8526169512885810416ULL,\n    10606277177867585391ULL,\n    17462320529339968987ULL,\n    9841461657138378404ULL,\n    12530621499994985368ULL,\n    12138394037526426204ULL,\n    1713329132912131441ULL,\n    11925969462168970433ULL,\n    1196117278294756762ULL,\n    15465615912611140029ULL,\n    16865448750301457966ULL,\n    13959079561515528205ULL,\n    17197070979728289415ULL,\n    9129899997424783581ULL,\n    16305277681370751852ULL,\n    7079385869117722687ULL,\n    14136526846777001970ULL,\n    5622873477415569546ULL,\n    14132074370034880822ULL,\n    14257227251381485304ULL,\n    15725972158050181847ULL,\n    16191468147976598955ULL,\n    14779586799596654434ULL,\n    17178872915791942893ULL,\n    15706585890681224675ULL,\n    6370337329812294537ULL,\n    4546624560547631037ULL,\n    7671355514835876872ULL,\n    6482591104502910504ULL,\n    18133818494665376690ULL,\n    5518697097150184817ULL,\n    11648765833865018541ULL,\n    5237191791472238656ULL,\n    16282740965922880917ULL,\n    11704435650846533959ULL,\n    9622902423791280227ULL,\n    1112814598050646685ULL,\n    11836726278245198239ULL,\n    17470347079497274103ULL,\n    11572814632807053691ULL,\n    3126426021795331508ULL,\n    16834068405210842666ULL,\n    16367898849264301694ULL,\n    14168272408053460200ULL,\n    17842879903250809374ULL,\n    4460459246723434776ULL,\n    17390839691096567097ULL,\n    5238320966107584217ULL,\n    804279582174123726ULL,\n    382643145896732389ULL,\n    7031560320750579182ULL,\n    2268109989486140767ULL,\n    10732868279561702107ULL,\n    15588189569888174340ULL,\n    15051374802410365535ULL,\n    11863487355546879331ULL,\n    5668954682138106931ULL,\n    14894969265510545190ULL,\n    9930937415817545832ULL,\n    12270273018688756969ULL,\n    6424275580222443892ULL,\n    15346649413470052925ULL,\n    6124876194997938686ULL,\n    15202298559304146220ULL,\n    9717206781603568070ULL,\n    17403037834543754494ULL,\n    8155862396012969616ULL,\n    2196965974337589297ULL,\n    6359186515398487765ULL,\n    6920699125992576275ULL,\n    2552656107685148741ULL,\n    9706571795437031658ULL,\n    17391266168587319952ULL,\n    2441904481355834235ULL,\n    9939734839946509364ULL,\n    16157918500405035385ULL,\n    5855974745488839504ULL,\n    8809728218402189346ULL,\n    3997781792457880401ULL,\n    3026726811101610212ULL,\n    10349627118579533940ULL,\n    652865985408329757ULL,\n    5660899694052107117ULL,\n    2481858537755811504ULL,\n    8823259127303582830ULL,\n    10495074797750663416ULL,\n    15611931697817472123ULL,\n    3736375065273859429ULL,\n    12082969862062340722ULL,\n    8192101484950684324ULL,\n    13563715416017446866ULL,\n    6447557136478108869ULL,\n    3177929464759158638ULL,\n    2556931193036154743ULL,\n    17374739867116517483ULL,\n    12943515678389628824ULL,\n    18233405297727696042ULL,\n    12987786471326508001ULL,\n    6402293521216520704ULL,\n    9844544932198108522ULL,\n    5840665125696484458ULL,\n    17618871334912345347ULL,\n    584036089002092265ULL,\n    3826372451367254979ULL,\n    13547732253342725230ULL,\n    9090088398589304368ULL,\n    13651088999521826549ULL,\n    11922118582127648293ULL,\n    16726162116612567991ULL,\n    173021407810447376ULL,\n    13023296345829657693ULL,\n    13395604246261145198ULL,\n    17704372952581520808ULL,\n    14145339548022084169ULL,\n    10547024225770925277ULL,\n    5188652153533062213ULL,\n    2369073765187618339ULL,\n    3585607009344477896ULL,\n    14466267149204275179ULL,\n    6439379484291556522ULL,\n    3466133161808725230ULL,\n    7592574339978489225ULL,\n    3296361696107442985ULL,\n    5629486732526367029ULL,\n    10178071873203748022ULL,\n    7836620321844132001ULL,\n    15832561432102095736ULL,\n    15789048915323099971ULL,\n    6978441970986331673ULL,\n    16543294822827063297ULL,\n    4001729876585152305ULL,\n    13167149618809565499ULL,\n    12351486316497308285ULL,\n    2168285614853559746ULL,\n    691662334599017619ULL,\n    7078956410680169520ULL,\n    15931487650081957445ULL,\n    10598208142670071070ULL,\n    14497058747951975037ULL,\n    10947328661084452789ULL,\n    12346641253943876196ULL,\n    16712960915509016730ULL,\n    18190650413003021162ULL,\n    2807603792126257040ULL,\n    10758546223867394756ULL,\n    92217154942760320ULL,\n    7309571029250507886ULL,\n    6130765302164479206ULL,\n    8370303958678535935ULL,\n    6876209718879309960ULL,\n    16726782578721505146ULL,\n    3211106240849817623ULL,\n    6008037829755053226ULL,\n    15581977177004958242ULL,\n    17171892203245079682ULL,\n    9979580141361963708ULL,\n    587448701938823063ULL,\n    13156953023564821908ULL,\n    9626116087985467825ULL,\n    14430208058868163728ULL,\n    13969197981666929019ULL,\n    1493656712749060737ULL,\n    943973757795526842ULL,\n    8360946925228878863ULL,\n    6340609169021813332ULL,\n    7698402192838266913ULL,\n    3547950907788145927ULL,\n    10125988332848537436ULL,\n    4268053794341246090ULL,\n    13605251106659994131ULL,\n    7825878450443999450ULL,\n    8902742129731191072ULL,\n    8460622463665615764ULL,\n    7876580965634993839ULL,\n    13453923412387957035ULL,\n    179794810042508686ULL,\n    10157815493870213921ULL,\n    9980085253668495848ULL,\n    3048760658759075617ULL,\n    1378357739699714861ULL,\n    11759580766373769453ULL,\n    1053286204682908267ULL,\n    15819615191596606337ULL,\n    14892138325878786511ULL,\n    12934757226770722084ULL,\n    273937187243421642ULL,\n    10087508769563436705ULL,\n    8761919875055770361ULL,\n    1431340134041776746ULL,\n    2615704612030635682ULL,\n    411109257764326709ULL,\n    13059876097172311106ULL,\n    5659009748861691970ULL,\n    6629700633104833081ULL,\n    12828953999979684528ULL,\n    6955966383889929295ULL,\n    3269704691566558524ULL,\n    7240571617038436830ULL,\n    15316273429789400393ULL,\n    17753495152232435094ULL,\n    2317057612399635402ULL,\n    3507492426240400459ULL,\n    15577479071427190734ULL,\n    16624612412411031523ULL,\n    14324334926671237511ULL,\n    55005550089714232ULL,\n    8207543599467475364ULL,\n    4404370892754459531ULL,\n    2560428258147803813ULL,\n    9682645457845509264ULL,\n    8928125739343013147ULL,\n    6710233698293424470ULL,\n    18394922620639371491ULL,\n    10742467300066031090ULL,\n    6834921168086442109ULL,\n    15437402565750985978ULL,\n    9298440047464458375ULL,\n    2805365750264488565ULL,\n    7729560059545738619ULL,\n    2103461790371736804ULL,\n    16275285124420485996ULL,\n    411746384530318601ULL,\n    13638448325396739538ULL,\n    7785494749810973100ULL,\n    5753704762731717790ULL,\n    299663777073461576ULL,\n    11047572734344411622ULL,\n    5833005655271187322ULL,\n    15633925920833652058ULL,\n    12708796682637478368ULL,\n    11358798529978425534ULL,\n    15353584282273949843ULL,\n    11674257421439531993ULL,\n    15533482268102463928ULL,\n    4377492385224474374ULL,\n    10044464015935306887ULL,\n    7680192936083965540ULL,\n    13838213572986632188ULL,\n    3746216992597880201ULL,\n    2531345133349587284ULL,\n    4167817782496222920ULL,\n    7541704319801109656ULL,\n    11502536999400839849ULL,\n    12433805204681247938ULL,\n    12203486111693024575ULL,\n    4838018977213282157ULL,\n    13455362964947194800ULL,\n    2524930298991450437ULL,\n    3365731876033633337ULL,\n    17840696255995219990ULL,\n    5055044690493673170ULL,\n    1994210558112178299ULL,\n    877859518894907480ULL,\n    10360514534730457706ULL,\n    13812314268590143916ULL,\n    12973075502205279686ULL,\n    9642563649435311801ULL,\n    6686859806080664539ULL,\n    1918737590075340668ULL,\n    14223518965800750764ULL,\n    8923776035028773399ULL,\n    8082553177219888377ULL,\n    16551570976778601324ULL,\n    6199514704888570297ULL,\n    9379273114158266126ULL,\n    8132750451833700491ULL,\n    15174536967701315427ULL,\n    13108703619856181167ULL,\n    9706895172766285330ULL,\n    13287295009451765088ULL,\n    7060006720104096229ULL,\n    15547993400500510910ULL,\n    3152863293760947472ULL,\n    10711108073776194194ULL,\n    11030853293456198570ULL,\n    9417511979289886739ULL,\n    3901932409507901091ULL,\n    2130555960651378199ULL,\n    7648022943207627164ULL,\n    11576084718197933542ULL,\n    3007687143675783151ULL,\n    2667284041385430459ULL,\n    7685525638058298493ULL,\n    440244184159234791ULL,\n    12775657703919498679ULL,\n    7960685070333844945ULL,\n    9081325433440124259ULL,\n    16506955021259853026ULL,\n    17145863478138494195ULL,\n    17542523179403292320ULL,\n    9920998098127465863ULL,\n    7855515761917124674ULL,\n    8377644556827237650ULL,\n    8299333371566803416ULL,\n    18429492874391446294ULL,\n    1083165370535998516ULL,\n    1266789808074555572ULL,\n    12450037707208327004ULL,\n    10627493659357899382ULL,\n    2667509059387534658ULL,\n    6706026483647579000ULL,\n    9777833515196960112ULL,\n    9177314826855846807ULL,\n    8028857461632562217ULL,\n    7600550706114967070ULL,\n    7601872075422039531ULL,\n    1026791241979300709ULL,\n    14771229760569606518ULL,\n    12863551060107893119ULL,\n    18257487233157711552ULL,\n    17239913281916521615ULL,\n    3584389773773061937ULL,\n    1706331133572750023ULL,\n    89221867743410950ULL,\n    9304876668775702936ULL,\n    12342157889814776723ULL,\n    13827018139374206506ULL,\n    11505786564322561755ULL,\n    15524515213869047249ULL,\n    1751921260424074909ULL,\n    13959897162133756498ULL,\n    11001709590168950151ULL,\n    8967575802201698328ULL,\n    13423672601977520913ULL,\n    13209192309367947513ULL,\n    8978847893107536378ULL,\n    14965716353394758094ULL,\n    6990213367993054273ULL,\n    14102968413577227966ULL,\n    10735252987548201178ULL,\n    11325153936290785732ULL,\n    10952265642388153073ULL,\n    13614151480120059109ULL,\n    14043495571678553199ULL,\n    11591292484451300719ULL,\n    15372622979936782085ULL,\n    6918697893134038430ULL,\n    17727317059109054673ULL,\n    13129752230461024745ULL,\n    18091426562073057528ULL,\n    16486478671684867544ULL,\n    14976000574156171936ULL,\n    6929927207671471076ULL,\n    12923356386927721413ULL,\n    10322662329451311593ULL,\n    13467816543454056173ULL,\n    3212284635638125670ULL,\n    3769759457453575706ULL,\n    13067015062744417527ULL,\n    12061425259542538728ULL,\n    6969116134649337727ULL,\n    4491452266391946955ULL,\n    11582471713435793814ULL,\n    3145822266295535261ULL,\n    2358960635244569410ULL,\n    12813014532981092208ULL,\n    17242039097139490960ULL,\n    18168979516335478789ULL,\n    4714407687464481916ULL,\n    6078434131117076308ULL,\n    7575556534676297678ULL,\n    3866649752098041549ULL,\n    4788152935683236961ULL,\n    7996601468066849438ULL,\n    10506532853031313598ULL,\n    823279797819684363ULL,\n    9857561386598593269ULL,\n    15128768365473859447ULL,\n    7870838550899552019ULL,\n    14303265666189456777ULL,\n    17225771712384627374ULL,\n    14321143804969213531ULL,\n    13928382936081411551ULL,\n    125404555640574459ULL,\n    3396150649711425399ULL,\n    3937469439294286815ULL,\n    13077295489689852802ULL,\n    7072434510961156367ULL,\n    17716049455592766085ULL,\n    1189210936305857040ULL,\n    803986269877148373ULL,\n    5923075891731922728ULL,\n    17598343695435984994ULL,\n    5555286758671978191ULL,\n    4287558852100868492ULL,\n    6234591414742396777ULL,\n    10032896413606109795ULL,\n    4948102257370528026ULL,\n    3962346168746322565ULL,\n    6792866022098760183ULL,\n    7496840472752724050ULL,\n    14047266419288473282ULL,\n    17689641818754147934ULL,\n    4280924343792801604ULL,\n    12917393697394781260ULL,\n    15398704948740098379ULL,\n    237919657965645623ULL,\n    449077133773936943ULL,\n    10564504801479163133ULL,\n    6186653685076469971ULL,\n    5642247834070865280ULL,\n    10862889033909590591ULL,\n    10460872049899274970ULL,\n    14480494971121185848ULL,\n    7674127105688418921ULL,\n    1483837077098188088ULL,\n    5579576753518742623ULL,\n    327403928124524675ULL,\n    1048652765325909944ULL,\n    9732906961508739748ULL,\n    6052160058422614539ULL,\n    9153637052954086956ULL,\n    4241663136948479588ULL,\n    4757250669355141570ULL,\n    10713383977092581835ULL,\n    14897980904418863173ULL,\n    2782409315404502595ULL,\n    3306623171766658624ULL,\n    16792642247981579707ULL,\n    8844818087104871090ULL,\n    15722850917996985635ULL,\n    4525339646839593853ULL,\n    2165871126735808843ULL,\n    489263708619032053ULL,\n    10022450408147499864ULL,\n    17775269356463302050ULL,\n    2889296419696964116ULL,\n    17990363573637051481ULL,\n    2597023539416840330ULL,\n    5683936714582039640ULL,\n    14530247612066141502ULL,\n    16574114334984760334ULL,\n    3521553201323381173ULL,\n    11614173340648119637ULL,\n    12791106730549499451ULL,\n    7702783699664053469ULL,\n    2155097561958662844ULL,\n    10891791160845644221ULL,\n    8319274981802036674ULL,\n    10988196830122188976ULL,\n    7376538179813516557ULL,\n    1102866567315922083ULL,\n    11512878802516763323ULL,\n    12425741229827666695ULL,\n    18336366052981587520ULL,\n    2118348952275183238ULL,\n    18033338004607460722ULL,\n    17316865190130567144ULL,\n    13771338478824527002ULL,\n    8472862867332243023ULL,\n    7901496556613372666ULL,\n    9852185765071539282ULL,\n    13267283636080418779ULL,\n    16950387508968781639ULL,\n    4092033603783467894ULL,\n    9219908345158155148ULL,\n    2311096345450122226ULL,\n    10120433178744606605ULL,\n    9685752505903662422ULL,\n    6047323209436860262ULL,\n    15465188139752016799ULL,\n    8028250806910547069ULL,\n    10951464862458759610ULL,\n    7349701937962425912ULL,\n    15908657631742751508ULL,\n    3338977238862330196ULL,\n    8553277017373603621ULL,\n    15455965377616646418ULL,\n    15530844264505781933ULL,\n    11406327456004149974ULL,\n    7193351823341580297ULL,\n    12268159561197947662ULL,\n    1598948486722442902ULL,\n    9734178213247413633ULL,\n    494946208623695703ULL,\n    6242217916679007984ULL,\n    13185877995528082216ULL,\n    2514014514725505370ULL,\n    6872771970752053768ULL,\n    1348320544999670881ULL,\n    5398133074672585721ULL,\n    4485817000488389585ULL,\n    16994647614840702108ULL,\n    8297041685642147927ULL,\n    3028383353663189262ULL,\n    1453734289836351329ULL,\n    2834045116388374515ULL,\n    9136403968924532574ULL,\n    15048244857565900711ULL,\n    1751240667656816069ULL,\n    6062222652262421222ULL,\n    15784168067075077348ULL,\n    816761595138178765ULL,\n    4534474478386745414ULL,\n    13781515075497821723ULL,\n    13342613786209482938ULL,\n    17453701765329728645ULL,\n    8151171140925638624ULL,\n    2922786353106499013ULL,\n    11900999437725651107ULL,\n    13945809928581273683ULL,\n    7802027295929165438ULL,\n    14924086231697963100ULL,\n    10913701766830368934ULL,\n    9777238489010221725ULL,\n    9997107583655984729ULL,\n    7590053360166699517ULL,\n    14955885736937498711ULL,\n    8714864102204299653ULL,\n    4056768185189398095ULL,\n    16800643623329583907ULL,\n    15874852558853506273ULL,\n    15527646554284333040ULL,\n    9875594063869406457ULL,\n    9771054165614466913ULL,\n    4855539746356941360ULL,\n    12985641122190453171ULL,\n    15047418131480883318ULL,\n    5033972233913973535ULL,\n    11992864504664703833ULL,\n    17772729344635645937ULL,\n    9580902613913802679ULL,\n    3612514584380675076ULL,\n    4461442915991505621ULL,\n    13938343423656538356ULL,\n    10935112568252155024ULL,\n    10725275106169791266ULL,\n    12464706577445911994ULL,\n    2009036350316964177ULL,\n    4244537232679209636ULL,\n    14899717590774522271ULL,\n    15334027381316242506ULL,\n    11908482308274900809ULL,\n    13632774467848960692ULL,\n    3024498040702583825ULL,\n    15477743557970797652ULL,\n    6064752903590408242ULL,\n    10842000201732452495ULL,\n    6412225338456765891ULL,\n    5857455038764354332ULL,\n    11741488095098810986ULL,\n    4658312818726858402ULL,\n    12962486790752943564ULL,\n    8859998448780066746ULL,\n    2767767966845634869ULL,\n    17989004387205099458ULL,\n    14360792151720641165ULL,\n    16797901722813283200ULL,\n    3850266765615321175ULL,\n    12958825873876665060ULL,\n    5378885436728422250ULL,\n    9430158636464686675ULL,\n    1677067699475902967ULL,\n    6152807634570655482ULL,\n    12632122686032526186ULL,\n    674804058271666904ULL,\n    8192525674517562401ULL,\n    5115351160260378919ULL,\n    3758887264943980972ULL,\n    16743990368080096080ULL,\n    13446632154750987521ULL,\n    8061757586363929481ULL,\n    1391647454027440361ULL,\n    2860893831986220355ULL,\n    2262925051424139552ULL,\n    10528437010123622553ULL,\n    12334944825220074911ULL,\n    6445320809537664583ULL,\n    5969947979382846752ULL,\n    6815644878948814139ULL,\n    14590032671250127116ULL,\n    9291195192806838215ULL,\n    17795534194487544282ULL,\n    15845355605400163842ULL,\n    15735938002942249878ULL,\n    2185953524748213386ULL,\n    8494886120825377793ULL,\n    477904482660169593ULL,\n    7212379354909644623ULL,\n    1865070808753012894ULL,\n    7803191509914197048ULL,\n    40069376576273197ULL,\n    9593713699013262266ULL,\n    2303211428596741253ULL,\n    17183177998218787491ULL,\n    1969526769533390984ULL,\n    5553633000056343299ULL,\n    10808031971135780913ULL,\n    1353166007908611502ULL,\n    7025219767362473626ULL,\n    12455899243491984486ULL,\n    813988564837822037ULL,\n    10130082485061864609ULL,\n    4474122430041291050ULL,\n    14684233305397693655ULL,\n    771366386026315466ULL,\n    16535437055852377240ULL,\n    14398293093768281038ULL,\n    15668432830562998633ULL,\n    15395060331245336140ULL,\n    5676422980506939035ULL,\n    18039045123533766501ULL,\n    639585958864861717ULL,\n    8226502996810335351ULL,\n    1939853363213990592ULL,\n    17136441807213227145ULL,\n    6512585125693071065ULL,\n    13810945258065130429ULL,\n    4925234401035825134ULL,\n    18202714893503733219ULL,\n    6296334705029916396ULL,\n    4695726586125540991ULL,\n    16781454468169027416ULL,\n    7731280292805062004ULL,\n    7188129480347582452ULL,\n    9117971445288370713ULL,\n    2530555638876198915ULL,\n    13404372431889828304ULL,\n    16874466351271862494ULL,\n    13253959773289443527ULL,\n    7856475753668315373ULL,\n    15655977194945010083ULL,\n    1537207384085023782ULL,\n    3481728681327757311ULL,\n    16771161183622917910ULL,\n    11951378022165942698ULL,\n    9617774942850888644ULL,\n    7747531162090216265ULL,\n    3545520387807433815ULL,\n    2197405516555076508ULL,\n    15079201025100918199ULL,\n    6288931882434851676ULL,\n    1560699106226986227ULL,\n    5872043722791498290ULL,\n    12387027819742630076ULL,\n    5841220061504552222ULL,\n    15596389618071387279ULL,\n    14966085889969527865ULL,\n    4660991993663118959ULL,\n    13229767849791128634ULL,\n    5657374588918090856ULL,\n    2550516954253481289ULL,\n    9201004109649753692ULL,\n    8184845602104040167ULL,\n    17390011871927406610ULL,\n    14635347065500267231ULL,\n    15269004136287979371ULL,\n    18429906313722199764ULL,\n    5495519432913393332ULL,\n    2357815369544670941ULL,\n    12854806502152344855ULL,\n    12176742726176523264ULL,\n    1430372901447596059ULL,\n    17581355612292133327ULL,\n    13019868030189287544ULL,\n    16821247737215005587ULL,\n    2705492589166809009ULL,\n    406233234325085315ULL,\n    10313768466749209551ULL,\n    7251266766986407823ULL,\n    10159671875868821000ULL,\n    16453124574420729018ULL,\n    16030163490611791314ULL,\n    15446847534330270494ULL,\n    10769108147592477673ULL,\n    697047151993978354ULL,\n    7277202607316926093ULL,\n    16143377228741024077ULL,\n    12126908386843641771ULL,\n    11328160218236161268ULL,\n    7428911124872267738ULL,\n    12480622834486186741ULL,\n    15476386830289252028ULL,\n    4330804178939170871ULL,\n    917895642729551698ULL,\n    10826029823079582211ULL,\n    7296103285531740735ULL,\n    12512714708841852467ULL,\n    14977017068812242585ULL,\n    18328787272256734709ULL,\n    9679229319848354290ULL,\n    951620572361468323ULL,\n    6155281346369236317ULL,\n    17452724970718365630ULL,\n    11471165883426482268ULL,\n    2068932383403115112ULL,\n    10987303892392051181ULL,\n    1105452545322201544ULL,\n    14814462596390239830ULL,\n    13817463470581064590ULL,\n    3185348475142831543ULL,\n    2684231028392439626ULL,\n    16368489961777153974ULL,\n    16541788652974985137ULL,\n    3577493263425020755ULL,\n    17702631528051100367ULL,\n    17188904736690863936ULL,\n    15497635176317074005ULL,\n    1722669088634598450ULL,\n    13053856558576894875ULL,\n    10365194786257117781ULL,\n    15208083129515418899ULL,\n    11699395792937341831ULL,\n    9450184079722412036ULL,\n    10279137405857516040ULL,\n    10888306426468547132ULL,\n    5648539157614273884ULL,\n    14504664740911952813ULL,\n    8340444004634755506ULL,\n    9553654456359369471ULL,\n    18415969399135437606ULL,\n    1089829165886876ULL,\n    1187794066021145903ULL,\n    8504764218087372678ULL,\n    10296784751923362354ULL,\n    14011938312753349668ULL,\n    3521226240215449834ULL,\n    1155908442676742760ULL,\n    16508688996574861463ULL,\n    4046795530128646034ULL,\n    2256091224581368161ULL,\n    10414603057682827043ULL,\n    10624516874793769568ULL,\n    11436463571818154810ULL,\n    3149278422181523015ULL,\n    12094499301886392632ULL,\n    11895166872864069099ULL,\n    15445688837827607391ULL,\n    12561122843165184530ULL,\n    11399689481376141498ULL,\n    3364184664385766994ULL,\n    13922353501185731628ULL,\n    5819494556654210069ULL,\n    2340263804446904473ULL,\n    14256681708204131248ULL,\n    12757680644233730143ULL,\n    9650378166736091392ULL,\n    16663105710936130720ULL,\n    15666599611464520839ULL,\n    2262018189430996373ULL,\n    17088830329331898360ULL,\n    13373858611530166261ULL,\n    693261321896374204ULL,\n    5014479171320657970ULL,\n    10723124401507522231ULL,\n    12808913369859239517ULL,\n    8947425054903605554ULL,\n    6992823561859747974ULL,\n    9656617278051271785ULL,\n    18422438902674080601ULL,\n    9346310326508733830ULL,\n    2465586800758884677ULL,\n    4724259221520307195ULL,\n    14900509712425587034ULL,\n    15811686239760841193ULL,\n    2658954143637700875ULL,\n    10233352841486995870ULL,\n    16089278544739472594ULL,\n    17398388011664825616ULL,\n    9168605762332126767ULL,\n    8689106367580033330ULL,\n    1551008274117355017ULL,\n    18192068283123754028ULL,\n    17257823232034399158ULL,\n    4403217569307963824ULL,\n    3143544209934004723ULL,\n    16699442099531285552ULL,\n    1322683710817441894ULL,\n    16132383755187385572ULL,\n    4715963141607907566ULL,\n    6088309674674901267ULL,\n    10935964635523403009ULL,\n    16937705981750363824ULL,\n    16677317176183584684ULL,\n    10334739175664580167ULL,\n    13245364342490407817ULL,\n    578696604421114052ULL,\n    5184839568758261086ULL,\n    715445734445806423ULL,\n    5756438856720126673ULL,\n    5069270844774398534ULL,\n    12162311476725314948ULL,\n    13888620899446829742ULL,\n    5440648290834427759ULL,\n    1179546201345864571ULL,\n    8076655944586413328ULL,\n    18182912746461692684ULL,\n    10224164967437787659ULL,\n    16903563177476147756ULL,\n    1829441738987155986ULL,\n    13159572589122715501ULL,\n    15761045890309617460ULL,\n    13307771184331469386ULL,\n    3606757056193547838ULL,\n    9425156101535907938ULL,\n    6326674334448362379ULL,\n    6561916175435693874ULL,\n    14920774597871188803ULL,\n    11635659428760873225ULL,\n    6318290540118789273ULL,\n    5995313048053390465ULL,\n    9429677512939480906ULL,\n    6798874524408791822ULL,\n    10257871843076423249ULL,\n    17844128445449493899ULL,\n    9534999243936829831ULL,\n    17144099578991901770ULL,\n    4904201931292808011ULL,\n    14569178382395764916ULL,\n    7159018911665463465ULL,\n    8140230784284775462ULL,\n    2008150088075807935ULL,\n    6993300572593769725ULL,\n    14767597392983942197ULL,\n    17763403966559189918ULL,\n    6271709850268479833ULL,\n    9780525143749043184ULL,\n    83256957380639589ULL,\n    741943723852905757ULL,\n    17899091254207328750ULL,\n    3969940406547851857ULL,\n    792925767903938331ULL,\n    17583439055629156215ULL,\n    14030413543262401993ULL,\n    12184005635228435754ULL,\n    15476207329842866131ULL,\n    11253491926918093321ULL,\n    5117300161541091944ULL,\n    14682289023843452843ULL,\n    12738901340967608967ULL,\n    14810573270526166595ULL,\n    6599174734255163614ULL,\n    14489081571422082069ULL,\n    2346234607488820391ULL,\n    7940517079616877987ULL,\n    14324892873596646079ULL,\n    15222126906831573561ULL,\n    7913010095481135565ULL,\n    8817813369309415179ULL,\n    8350125465393601022ULL,\n    18178047288320379339ULL,\n    10835623418745774879ULL,\n    2814106181019246941ULL,\n    7976903684406235317ULL,\n    5861043930025569749ULL,\n    7996471993190633353ULL,\n    11113577592640770827ULL,\n    18953340064802113ULL,\n    13807810151508650600ULL,\n    5652682471736763863ULL,\n    567409500482036178ULL,\n    1552548337141446055ULL,\n    7958770766922705094ULL,\n    9577288305714753195ULL,\n    9975830208336251858ULL,\n    5445584802296000319ULL,\n    15979224632986974259ULL,\n    4082007551415002919ULL,\n    3558668096404020578ULL,\n    16467573595576885763ULL,\n    8048914519610134171ULL,\n    6116954885839784197ULL,\n    748425034809988121ULL,\n    5297025509697421798ULL,\n    5171757525804160082ULL,\n    11388129932302224637ULL,\n    135974319098806564ULL,\n    16685779012383685843ULL,\n    13103667156860588797ULL,\n    5474263352075413784ULL,\n    16237110213106344388ULL,\n    9523517418617636759ULL,\n    4178328570922750725ULL,\n    12294781721574936102ULL,\n    7291529598641943287ULL,\n    6793069115073391233ULL,\n    18417286838782639661ULL,\n    988706220942524236ULL,\n    16494019901179939068ULL,\n    2776520897796416256ULL,\n    1033009221664655109ULL,\n    5078098025876179018ULL,\n    4295083472261997887ULL,\n    7956637826954024726ULL,\n    12055596634545353497ULL,\n    10244481968966192230ULL,\n    13973285589182757339ULL,\n    5445984843860700994ULL,\n    10197735958096192370ULL,\n    8282975945415316112ULL,\n    3430251493364323886ULL,\n    2636404694429143299ULL,\n    10905332898631014978ULL,\n    3326915930450312102ULL,\n    16728005257542966759ULL,\n    4072951372654104776ULL,\n    6253190044084695187ULL,\n    3917996529398023244ULL,\n    12160269792085582131ULL,\n    2755849692529783919ULL,\n    17159768311873594495ULL,\n    6852240006851999173ULL,\n    6807602927507567074ULL,\n    8954509838974228037ULL,\n    15400693985846288449ULL,\n    700684598026766866ULL,\n    13721032953788907081ULL,\n    1296918612925269625ULL,\n    15733006908761235131ULL,\n    16502891145549199597ULL,\n    1956831145557982855ULL,\n    15769327680528030145ULL,\n    17504956797743066611ULL,\n    3367727739860402851ULL,\n    7594234119895895494ULL,\n    2548835308041708912ULL,\n    8830070231595888835ULL,\n    15218855315423367211ULL,\n    11935496554385838695ULL,\n    13074856169955382052ULL,\n    1334297964312791780ULL,\n    9133913577425834496ULL,\n    14249698395371498104ULL,\n    372294027268755456ULL,\n    9911915515031418466ULL,\n    15809032220143070565ULL,\n    12460788556699455949ULL,\n    16723831277861437487ULL,\n    15259051474383415094ULL,\n    13366539795142296637ULL,\n    15088451259421164579ULL,\n    5256331182759571756ULL,\n    10282334631932213558ULL,\n    10871138445427778436ULL,\n    3206226566004247310ULL,\n    2459234329792504867ULL,\n    9003019870745026647ULL,\n    6569797083804191807ULL,\n    6520458070196844843ULL,\n    18181091813259197611ULL,\n    14636991714448807086ULL,\n    10809641390161193911ULL,\n    6866210578179986741ULL,\n    3468845822108893326ULL,\n    14480104814431683061ULL,\n    15138990259586220246ULL,\n    7305922389263898008ULL,\n    11484358740887562450ULL,\n    15825076657531556860ULL,\n    14601713131043546273ULL,\n    12717604332822212064ULL,\n    17285912191272877428ULL,\n    12305813653905088998ULL,\n    16791101717542584700ULL,\n    13739716529888735272ULL,\n    5333167666677422702ULL,\n    3785723354136361884ULL,\n    5539671518314289143ULL,\n    442658735595423942ULL,\n    6392020646017671383ULL,\n    16178697939789293132ULL,\n    14820222883507623125ULL,\n    9449763778559715036ULL,\n    7579618059667070361ULL,\n    5779143146714968220ULL,\n    9801706916698406240ULL,\n    10926524956500774905ULL,\n    4521213001454393562ULL,\n    10180770180874357456ULL,\n    9299341434841857482ULL,\n    5105139903635994607ULL,\n    18215045920336678961ULL,\n    16824787378390103680ULL,\n    1561485762857015986ULL,\n    13509287182023935589ULL,\n    6637919154593321922ULL,\n    16752794319501500463ULL,\n    11753396954689760412ULL,\n    2578929706322444072ULL,\n    14294182185187086717ULL,\n    1284317915139324319ULL,\n    14836051037113268645ULL,\n    7147314691265851215ULL,\n    1977916644858708137ULL,\n    5407957272651073980ULL,\n    16478961722134403279ULL,\n    448324758988060880ULL,\n    9296278620717142525ULL,\n    17667973069005530023ULL,\n    17321301663801212619ULL,\n    7700258617698292174ULL,\n    18075830820136532401ULL,\n    5129672577202515997ULL,\n    9527976848665989806ULL,\n    4602854401342521302ULL,\n    10761825511470470158ULL,\n    13318674223840545575ULL,\n    14927630321371516483ULL,\n    9066435573359101873ULL,\n    7628238684950467005ULL,\n    14360919074309353401ULL,\n    3725516593862846509ULL,\n    711798305904845378ULL,\n    12129145236756859161ULL,\n    8363001574736063654ULL,\n    12009005718292617913ULL,\n    16963444433617107147ULL,\n    3227548205257503038ULL,\n    5850140091721369489ULL,\n    7481660875056912223ULL,\n    310622245754574257ULL,\n    13669276426761419691ULL,\n    10539094099010629801ULL,\n    11496510928444281563ULL,\n    11567854588103676450ULL,\n    15437681226057428888ULL,\n    16721704768490813109ULL,\n    13545106394425944507ULL,\n    13472661449244455447ULL,\n    2740659738249193687ULL,\n    6940195244722776160ULL,\n    17551121690504463691ULL,\n    8405903893303112539ULL,\n    6351498971065207551ULL,\n    7331391462115598948ULL,\n    17120503773676156620ULL,\n    15819875446437677197ULL,\n    17120503773676156620ULL,\n    13731173786604973492ULL,\n    146258855674464274ULL,\n    15677886690634020777ULL,\n    1132762110576260518ULL,\n    13392547991668367415ULL,\n    9241670268609416412ULL,\n    13446203889843457684ULL,\n    3880031649410167688ULL,\n    18191785201461428376ULL,\n    4489264282744631629ULL,\n    2296295843144459280ULL,\n    196966345534845565ULL,\n    455453292631051075ULL,\n    17789369135806005789ULL,\n    11756799960996204791ULL,\n    9040551916821593478ULL,\n    12650733727462400986ULL,\n    5020083589835693221ULL,\n    11851946519941575179ULL,\n    8169000187550643169ULL,\n    2828121828594382383ULL,\n    18225988676327529074ULL,\n    5457075905965756018ULL,\n    9254226347118384978ULL,\n    1687142658007595333ULL,\n    15953286494240873026ULL,\n    13515658327032498954ULL,\n    15253298575753614323ULL,\n    14780131331311627973ULL,\n    10069623596928559043ULL,\n    8132809830614009693ULL,\n    12567022996883089451ULL,\n    1337168788538487544ULL,\n    12641218689559489380ULL,\n    17446863119231669851ULL,\n    12039811720703761553ULL,\n    7137478950133823148ULL,\n    17071051149055422947ULL,\n    1109121436604050340ULL,\n    14078959672648114043ULL,\n    7155747468615156209ULL,\n    2553350700241628189ULL,\n    4927995263577092397ULL,\n    16324239418333927161ULL,\n    11166329211705286472ULL,\n    2152300038479170395ULL,\n    11193751168836769325ULL,\n    3656836303881079130ULL,\n    8897743663916133738ULL,\n    5607998051741345518ULL,\n    16193883563862256503ULL,\n    16719006980142586198ULL,\n    3871111842167626960ULL,\n    13996139097301588410ULL,\n    7971166347432570063ULL,\n    12325759512272791623ULL,\n    11388646155092299170ULL,\n    7924743010107696283ULL,\n    9351211303933353072ULL,\n    7199687931060128170ULL,\n    13318843157644312717ULL,\n    8777084840139312343ULL,\n    7395004160689754912ULL,\n    3518660993910313795ULL,\n    9338243288200251581ULL,\n    3999915306501197216ULL,\n    14857076738989482976ULL,\n    14478930027425962019ULL,\n    3007695521711326376ULL,\n    16246537312895901452ULL,\n    7510149809767402414ULL,\n    1736466410522444866ULL,\n    16357258260569222648ULL,\n    5723261503627093972ULL,\n    7270641590488042167ULL,\n    10740241259666478142ULL,\n    358538029265080173ULL,\n    17858178807885198266ULL,\n    13039182213461111945ULL,\n    12251816841814996995ULL,\n    18104063710056035657ULL,\n    10416970956730607074ULL,\n    12377965698588495915ULL,\n    11297511364486382612ULL,\n    7475319219115718167ULL,\n    17926785947245169052ULL,\n    12347346061923667426ULL,\n    2565491103604593898ULL,\n    7294887076158248212ULL,\n    3108230885488207361ULL,\n    17264291989559541030ULL,\n    6072290755091790213ULL,\n    1294806680687551016ULL,\n    12741917918371279375ULL,\n    667250430632121258ULL,\n    7973997519928711511ULL,\n    14693914285840381047ULL,\n    18439984004549887129ULL,\n    11949268265693408264ULL,\n    1258850354355180853ULL,\n    7718500876329371492ULL,\n    6336482824050737631ULL,\n    13560910770277686995ULL,\n    12709992059818000108ULL,\n    3716666045711940591ULL,\n    14071970313285143487ULL,\n    11199344083113594499ULL,\n    4516902589739692858ULL,\n    6697864101108723493ULL,\n    3931484023808215520ULL,\n    15420591318354810402ULL,\n    18120146173224564924ULL,\n    396890527290010211ULL,\n    13041122055766962818ULL,\n    12357660549794721641ULL,\n    8842475626833725699ULL,\n    18363221204967686364ULL,\n    9000087578302077797ULL,\n    8396791438810111190ULL,\n    16104845001429102064ULL,\n    6597335780905594619ULL,\n    8087604609482250757ULL,\n    11337626243478354270ULL,\n    2616078833293994928ULL,\n    8343213078985730442ULL,\n    280896761569618065ULL,\n    2472005528706721771ULL,\n    767086061994195543ULL,\n    11362539100360299413ULL,\n    6509528056869713549ULL,\n    9799949706135235177ULL,\n    11794135999140623334ULL,\n    1186883323292313835ULL,\n    1402251958721428408ULL,\n    17684506670234538905ULL,\n    14490425537487787462ULL,\n    18387932802918894391ULL,\n    4224387220678079087ULL,\n    3779401754901312320ULL,\n    4279627536915868106ULL,\n    14831708664027458294ULL,\n    12180566950545649970ULL,\n    6989125644315029848ULL,\n    5679880325577407312ULL,\n    4886190725919778567ULL,\n    2995736634151280868ULL,\n    1842731432512977019ULL,\n    4285331944586463299ULL,\n    12158395382656641781ULL,\n    13205746328533325845ULL,\n    1897104316585098545ULL,\n    4260022622688520643ULL,\n    3659511840172328413ULL,\n    9611272828836084151ULL,\n    2728393795730389366ULL,\n    14822028840618428260ULL,\n    5100808094142299866ULL,\n    7522783405138268531ULL,\n    13487145179116882860ULL,\n    6313935865828285986ULL,\n    11367971724756475207ULL,\n    17864297793310391248ULL,\n    12336318799178954191ULL,\n    16155221362471073301ULL,\n    714212726269508100ULL,\n    15396114840369713291ULL,\n    1300957759838301490ULL,\n    17053933805600587265ULL,\n    14174522544396862951ULL,\n    12600807741090437102ULL,\n    13589292355732039890ULL,\n    10324062787388533443ULL,\n    7841228355943969701ULL,\n    5790662469254770509ULL,\n    16647139223178760688ULL,\n    13487145179116882860ULL,\n    6369431948010286676ULL,\n    8161441478859771203ULL,\n    7954245161011870145ULL,\n    17442131496099297469ULL,\n    3744534407808355214ULL,\n    18277984890180908139ULL,\n    11474093195483400651ULL,\n    15247394080151408103ULL,\n    18048870528948392507ULL,\n    11846451780362262406ULL,\n    5977486331726161248ULL,\n    18326132143905279374ULL,\n    16451980689455935873ULL,\n    207030992414673837ULL,\n    1979963282685217456ULL,\n    3832943377478615858ULL,\n    220163787915455019ULL,\n    14890599941129225171ULL,\n    6592332268645825211ULL,\n    17842516024248391714ULL,\n    10005925239862749715ULL,\n    9519237936668610912ULL,\n    12386782535519462674ULL,\n    10035940286747600198ULL,\n    645739593613913499ULL,\n    10905792556632378976ULL,\n    6624877445356988520ULL,\n    8321137467649093742ULL,\n    234907564831130791ULL,\n    11996837367417375022ULL,\n    16718841558051108312ULL,\n    13589104003690428232ULL,\n    18372235925719160987ULL,\n    792430413454161082ULL,\n    7784926393360235928ULL,\n    3252593725550336129ULL,\n    15642046942067528906ULL,\n    4189429409271947388ULL,\n    13563349798547284658ULL,\n    11132403908539775750ULL,\n    5111431536044618439ULL,\n    13276353361583779745ULL,\n    1327227078252723094ULL,\n    2550322210088400447ULL,\n    3416820094170741318ULL,\n    12154378950763869505ULL,\n    4842188018010896687ULL,\n    14391262535675189583ULL,\n    12660461709226987251ULL,\n    16082454056620536820ULL,\n    6777229081074789029ULL,\n    1955582546074618535ULL,\n    5377307359703458557ULL,\n    2834586500991459245ULL,\n    8835083381988058220ULL,\n    457043112118462180ULL,\n    9139068565196588710ULL,\n    14099600258880096539ULL,\n    10079572935912006876ULL,\n    17954729550472671842ULL,\n    13463345547394735128ULL,\n    6001186992400010689ULL,\n    10518827080575948464ULL,\n    3428786255299711846ULL,\n    8090820892181338144ULL,\n    105877577554339197ULL,\n    16783040591103657683ULL,\n    14265182005372556661ULL,\n    12307889867544011161ULL,\n    14065937616591227356ULL,\n    7530952475121469983ULL,\n    15431212762049122847ULL,\n    15877608319203836852ULL,\n    200881052012446098ULL,\n    14994448637621805761ULL,\n    14095952701920123913ULL,\n    3520739413492026376ULL,\n    2533287030119510660ULL,\n    6332255748871657388ULL,\n    9552805879725581571ULL,\n    1940440615593134682ULL,\n    10541229323642211281ULL,\n    3805380965143227175ULL,\n    9804493742135096124ULL,\n    15154667584812520705ULL,\n    17778289410823831894ULL,\n    8635135639380924097ULL,\n    3052138376564076036ULL,\n    6546723033718097386ULL,\n    16626244033580007876ULL,\n    315547370034080122ULL,\n    13788235191220273834ULL,\n    12969768066762158850ULL,\n    14863671826123609192ULL,\n    1998476212610279451ULL,\n    10773846406597653115ULL,\n    9066861429166542057ULL,\n    17907030213514991465ULL,\n    6387472312344054593ULL,\n    3260802980721285854ULL,\n    18022761819532226771ULL,\n    3592026814186427273ULL,\n    17708932233343992669ULL,\n    13141365424639341583ULL,\n    8860102317456951515ULL,\n    5376231239164196174ULL,\n    14291548120562853131ULL,\n    11267097086692193669ULL,\n    16940660638208409658ULL,\n    6852852981769577236ULL,\n    8847637188831145873ULL,\n    11035431076183023052ULL,\n    17994623054672747402ULL,\n    10664052708031164888ULL,\n    14814801694342897889ULL,\n    11069641866218242831ULL,\n    15833115684663434654ULL,\n    945765000977867018ULL,\n    11479370278874952284ULL,\n    16898429249779860247ULL,\n    7170275243251157300ULL,\n    10234444154862377494ULL,\n    6961677769416078942ULL,\n    7435752138590705461ULL,\n    2158030254801183399ULL,\n    6975806076642828564ULL,\n    10394194913390387173ULL,\n    14189443850746438797ULL,\n    14940925902461184815ULL,\n    11225895788900758149ULL,\n    9066521065986960394ULL,\n    8326199567061031361ULL,\n    3679762955437140140ULL,\n    14029513716160072437ULL,\n    7168488559595090384ULL,\n    3338453788146874472ULL,\n    6629856861486850267ULL,\n    1875553928598058432ULL,\n    18402694373035106869ULL,\n    421155827412389818ULL,\n    16190726277946786637ULL,\n    4710379397870268055ULL,\n    9164999575071029220ULL,\n    16038203132229591188ULL,\n    17003373143843175736ULL,\n    4409191420398637121ULL,\n    13357043228190529726ULL,\n    13602814643861320014ULL,\n    16040053258643381594ULL,\n    16467428079409753163ULL,\n    13850364476037957401ULL,\n    7749952067094735453ULL,\n    14110109705876257160ULL,\n    1903701109181662906ULL,\n    10248123369965291333ULL,\n    16404788228487311492ULL,\n    3064972845250299211ULL,\n    11090403051420606019ULL,\n    500049940675931179ULL,\n    18188441259588287962ULL,\n    16849280833070737752ULL,\n    227045780727455608ULL,\n    951685701403073397ULL,\n    13911874150405221784ULL,\n    2572057153955426911ULL,\n    3720859935862321487ULL,\n    6495519389856423793ULL,\n    9234131622593152370ULL,\n    3466481472019429970ULL,\n    14252219118686253914ULL,\n    17106033340395136187ULL,\n    11978357875736602602ULL,\n    13139438446929933943ULL,\n    5066644223176469744ULL,\n    8477484223270919353ULL,\n    10766653442077782969ULL,\n    13524848396545128542ULL,\n    274358138526114497ULL,\n    11535066950645896921ULL,\n    14983689290132618417ULL,\n    6747033478743033486ULL,\n    3860455299042526670ULL,\n    1599533001702829070ULL,\n    8962662634789107827ULL,\n    6623392770382786250ULL,\n    9250059520757083784ULL,\n    4923986323416474147ULL,\n    17629600102556914485ULL,\n    17170297918889620491ULL,\n    13473444096831679337ULL,\n    13444238614376067370ULL,\n    14120876613179764318ULL,\n    1244976767353240573ULL,\n    9394921469409983459ULL,\n    3994948826979234746ULL,\n    2002600980421641412ULL,\n    12559221898510869058ULL,\n    1613993286205484233ULL,\n    8144692387383153532ULL,\n    6633758622439859049ULL,\n    17485889858698907775ULL,\n    7692505586363575918ULL,\n    8639689708408851298ULL,\n    13920377832442449693ULL,\n    4278332190572080284ULL,\n    3741019683082796893ULL,\n    15860143028341501357ULL,\n    1008992732384545452ULL,\n    16642479954927891343ULL,\n    1665669976591201392ULL,\n    6935950096032710904ULL,\n    16331113891087679341ULL,\n    10989979876102920602ULL,\n    7163317770378673812ULL,\n    16047149927928621075ULL,\n    5571833412522306314ULL,\n    7142090221573273577ULL,\n    3765978229372855634ULL,\n    10572752186498234368ULL,\n    13250907027028076834ULL,\n    8477062277550035563ULL,\n    14292940223241090532ULL,\n    17860735602257198005ULL,\n    6941078352177875886ULL,\n    16059926665463951116ULL,\n    7101188684534582141ULL,\n    1727072590381782401ULL,\n    7707758754092453827ULL,\n    2890101812173096397ULL,\n    3521431793860302036ULL,\n    8634076523875386288ULL,\n    2495654048573400855ULL,\n    4290246958955697444ULL,\n    9495070237147945489ULL,\n    18296130019164219800ULL,\n    14264816563503588909ULL,\n    16604417345988099355ULL,\n    4565373179533231321ULL,\n    1385919672585369624ULL,\n    16923276443918929071ULL,\n    548294452068151319ULL,\n    5596510063375992728ULL,\n    6137383766799473747ULL,\n    11466899945950374202ULL,\n    12686140761834221611ULL,\n    2605801439390047595ULL,\n    12916987317243018797ULL,\n    17068654972997551516ULL,\n    15934010062770647403ULL,\n    1867986544728141729ULL,\n    1840716017189797151ULL,\n    14253930935930933050ULL,\n    17260646180584086994ULL,\n    16463803460626779249ULL,\n    2095178263557159930ULL,\n    14194155688695516401ULL,\n    14104122209801689782ULL,\n    8700889757688053650ULL,\n    15715857349073649318ULL,\n    11075367046159303384ULL,\n    963046205704765222ULL,\n    10383293276269327833ULL,\n    13586076235105499504ULL,\n    17185087463987752085ULL,\n    18063372420796646872ULL,\n    16517275838011591014ULL,\n    15632805699274799353ULL,\n    14973768003884950962ULL,\n    3549388992979422549ULL,\n    2942134343540767115ULL,\n    16098508825355365027ULL,\n    3323808569965745094ULL,\n    1704607023681899734ULL,\n    17319616796868763224ULL,\n    375432592521788815ULL,\n    5760273901536330192ULL,\n    4090790746191589671ULL,\n    17254769855745716197ULL,\n    486768865300230170ULL,\n    9301706573264932713ULL,\n    1139152886227878309ULL,\n    15735547617260398374ULL,\n    3594616469006494624ULL,\n    7921668390096544384ULL,\n    3008839812542216275ULL,\n    16593937057240667212ULL,\n    32284676426371340ULL,\n    3421612789355890815ULL,\n    7724482408888646528ULL,\n    6654813525586852917ULL,\n    16836249105172680355ULL,\n    4247914132627837786ULL,\n    11996385595291419541ULL,\n    12907171797899493236ULL,\n    17923767388292200031ULL,\n    13897532453007999147ULL,\n    14765157715938141601ULL,\n    11111239236551554712ULL,\n    7862008834310513143ULL,\n    18293205773557933630ULL,\n    5556466416148372685ULL,\n    10211100778469828838ULL,\n    493525773798057518ULL,\n    14943471122109586740ULL,\n    14443654341571537589ULL,\n    13897968727775390301ULL,\n    15661069127571872568ULL,\n    3603861280518876179ULL,\n    5657079779138665910ULL,\n    11585322321711042801ULL,\n    17768548524682436543ULL,\n    9035138149036189622ULL,\n    9855706826507483495ULL,\n    1163103078605792125ULL,\n    16822413654290555685ULL,\n    11217193697645937540ULL,\n    1716618719896887530ULL,\n    71266925529139540ULL,\n    11786859973617655103ULL,\n    2917892999575394459ULL,\n    16131768174980169434ULL,\n    11159537061590158982ULL,\n    2111639572037176831ULL,\n    14392524017528474368ULL,\n    4341981226786043515ULL,\n    1220045281197240578ULL,\n    6690135658734550109ULL,\n    15950447617188370613ULL,\n    9008074978545566037ULL,\n    14082049708616927281ULL,\n    10967847919791717902ULL,\n    9003056100442792240ULL,\n    9598570795569517452ULL,\n    17104088356956588319ULL,\n    8536586054020240228ULL,\n    14853849630443574947ULL,\n    500879355572580303ULL,\n    10046374512757336728ULL,\n    9225803724444505731ULL,\n    13283648443209110879ULL,\n    7122452886376861180ULL,\n    18194711026513019100ULL,\n    10133803816096608256ULL,\n    11836739294723586683ULL,\n    1035744546769052370ULL,\n    9922366699099137817ULL,\n    7393420143186982672ULL,\n    10544829186597024313ULL,\n    13171880853985201048ULL,\n    8040102697882199382ULL,\n    13700104392697655031ULL,\n    734050986077465865ULL,\n    15176283758881611702ULL,\n    96494418923886932ULL,\n    2736366661004030445ULL,\n    12361717417702962500ULL,\n    9404793695961591029ULL,\n    1323795654699027466ULL,\n    10209213992611864484ULL,\n    17437138238579047048ULL,\n    15289960472621792479ULL,\n    15038180696495039679ULL,\n    2914447783388839241ULL,\n    15843928410797591259ULL,\n    10514139925996907745ULL,\n    6692277600186155441ULL,\n    3857186284943242586ULL,\n    1727361650005981655ULL,\n    17935520478862402188ULL,\n    12668675625860210741ULL,\n    11570535653246665849ULL,\n    8862480513639759917ULL,\n    9754967970471888710ULL,\n    8028269968669568115ULL,\n    9457487591245134004ULL,\n    9391978495557779020ULL,\n    432687230653698492ULL,\n    8281805608676996124ULL,\n    9680558705220163702ULL,\n    11815699030930260668ULL,\n    4696553301762525581ULL,\n    12191703266865224025ULL,\n    10932284033808189413ULL,\n    17955387299755657539ULL,\n    7765842881924368718ULL,\n    17967782035427808474ULL,\n    5900297614114100070ULL,\n    3549149894511081576ULL,\n    14176073969488544732ULL,\n    3621748162985565988ULL,\n    8554941796094555879ULL,\n    10922985632040796561ULL,\n    7535925828011626524ULL,\n    4672577974505829359ULL,\n    16894602154707159322ULL,\n    860217380718535236ULL,\n    5553393167245659064ULL,\n    11611029189171597176ULL,\n    2934744362212727054ULL,\n    15380557854424639211ULL,\n    12818908336130698227ULL,\n    2123144833909283852ULL,\n    11320085460890676657ULL,\n    13134082906746001997ULL,\n    11345243675496506805ULL,\n    7318824140666418585ULL,\n    12624419592529831517ULL,\n    8477500727986965571ULL,\n    3259776827704926790ULL,\n    9562874361835031766ULL,\n    18104207838650914473ULL,\n    6438635252556897213ULL,\n    8306992085424275369ULL,\n    16415174088350956575ULL,\n    15681212344933142812ULL,\n    13846817173868647884ULL,\n    4129885096112629754ULL,\n    12714037692644669515ULL,\n    5669721918023650147ULL,\n    10332269748833396841ULL,\n    14036436395324823434ULL,\n    14997023042778913309ULL,\n    14983853063737406531ULL,\n    17101693105276283801ULL,\n    6998928353304973586ULL,\n    11760290072981044746ULL,\n    4677739490219384527ULL,\n    3745926692709784941ULL,\n    13642573358654372992ULL,\n    15707082301286080379ULL,\n    2788051994698637345ULL,\n    17377585851599032569ULL,\n    17376392326093277434ULL,\n    3068579427660274801ULL,\n    6079524568539044012ULL,\n    4375456885136429053ULL,\n    8167579976708058561ULL,\n    9109065524326828106ULL,\n    8847103179269665092ULL,\n    17232374073463673435ULL,\n    4062421742108180330ULL,\n    3167438977687498536ULL,\n    2163267231947563279ULL,\n    17376866563795550126ULL,\n    3153372801006664537ULL,\n    10373581928819888150ULL,\n    8250394076845661716ULL,\n    11860753088711492073ULL,\n    4233968564218513597ULL,\n    12481581661439360825ULL,\n    16495835167522175515ULL,\n    18414649866935522162ULL,\n    1903163314599471107ULL,\n    13027859011763496119ULL,\n    8896292901086887490ULL,\n    15174661417798797761ULL,\n    8504339157127487465ULL,\n    8453925592145746277ULL,\n    6374604919926706321ULL,\n    15179594231668171679ULL,\n    2963840706624820138ULL,\n    9303117877193312700ULL,\n    3483494314882833135ULL,\n    17784178289958512937ULL,\n    3031721478869120641ULL,\n    4609847194791492140ULL,\n    17166997457069387010ULL,\n    9444504467686143002ULL,\n    6271170356940453894ULL,\n    17487908678659842965ULL,\n    15317494335457263658ULL,\n    1182610092390832446ULL,\n    5935271851307418831ULL,\n    7727886665915933998ULL,\n    454200888729186931ULL,\n    12791517825571331884ULL,\n    18198538789413397493ULL,\n    2215040003277972344ULL,\n    15424679668598492114ULL,\n    9854987959370975231ULL,\n    7940679909968567560ULL,\n    13771584842524801381ULL,\n    7023403070003419429ULL,\n    7344855298028115259ULL,\n    8419211386511864272ULL,\n    1333806107532203782ULL,\n    183354088383355439ULL,\n    9773162973099835149ULL,\n    8020673822942330678ULL,\n    4992886405398866470ULL,\n    16345779068781090887ULL,\n    14722491054754389054ULL,\n    4576955606816027523ULL,\n    17604097467096621874ULL,\n    7918188732866015129ULL,\n    13165548585025934618ULL,\n    8274779233481886237ULL,\n    6062786993582542042ULL,\n    17139776791299126541ULL,\n    10822759937912541795ULL,\n    1958073158696438166ULL,\n    8966270733143033845ULL,\n    11688192419318207531ULL,\n    16323366147386797698ULL,\n    4515059919199649508ULL,\n    10914154998019845787ULL,\n    2599899248848360820ULL,\n    5983074539588494639ULL,\n    8527912050202368362ULL,\n    11159966218756818568ULL,\n    18271294946948351015ULL,\n    18305712251398725854ULL,\n    642524466010624959ULL,\n    18292005297901477484ULL,\n    890054661296057286ULL,\n    2974106595861660097ULL,\n    9060518230027282149ULL,\n    6768084551966558121ULL,\n    10445824701136367595ULL,\n    10427527024141200938ULL,\n    8154189548674371782ULL,\n    16088943159720929477ULL,\n    5425312852382594669ULL,\n    18173883271754751771ULL,\n    15082009850260307406ULL,\n    3387553581881884273ULL,\n    5115172809763590366ULL,\n    7653945281869207867ULL,\n    609106497710682503ULL,\n    7690402719979114623ULL,\n    9810288536073514827ULL,\n    17586323831141690124ULL,\n    15828597198533340748ULL,\n    16181039404381803771ULL,\n    15917025184660064114ULL,\n    2520111147273955708ULL,\n    4981046865102453303ULL,\n    5986967429679794112ULL,\n    11608577891495764142ULL,\n    13148720240238505632ULL,\n    11482696272457418307ULL,\n    6694434523878309067ULL,\n    16103580274653275342ULL,\n    5929827671661831745ULL,\n    2812038691491496836ULL,\n    3671483801832904958ULL,\n    4868051210139487664ULL,\n    17313306531137796902ULL,\n    6664264298130342748ULL,\n    14168708499334435787ULL,\n    17450646835869820632ULL,\n    13236683175038612539ULL,\n    9242318703717094671ULL,\n    4553804930871631271ULL,\n    10751339769153508878ULL,\n    10697974930743060307ULL,\n    8856685116641403638ULL,\n    7180223139314881739ULL,\n    17755065622354217909ULL,\n    575400529060771341ULL,\n    10930490573439720237ULL,\n    16973772172559421444ULL,\n    18276224364254662240ULL,\n    4800882629767626077ULL,\n    13960891486251827794ULL,\n    14441537660376559938ULL,\n    6538689969154493913ULL,\n    16516562346894563247ULL,\n    13288641520473481394ULL,\n    9630431586182629945ULL,\n    14990377005182345611ULL,\n    3643364070516786588ULL,\n    5896246632428518249ULL,\n    13483925498841891079ULL,\n    60805129681855169ULL,\n    779179576490234872ULL,\n    3874840407299712570ULL,\n    5628017066921385753ULL,\n    13880727252362387537ULL,\n    3065143959891218903ULL,\n    2090989375596490548ULL,\n    11651778952894044659ULL,\n    366729735634408585ULL,\n    10561471638962447748ULL,\n    7656715166813668741ULL,\n    4313202424079034079ULL,\n    9626162760195576614ULL,\n    9283381549266468440ULL,\n    260804798711224734ULL,\n    13935819933372908322ULL,\n    1176694405652734032ULL,\n    16850037808392704109ULL,\n    4263551166390619994ULL,\n    3909891896942740357ULL,\n    16381974289227186030ULL,\n    1752833768933025333ULL,\n    14152378374445194947ULL,\n    12689852080095404340ULL,\n    13954637176368028688ULL,\n    3107803324905289919ULL,\n    16407232286541083176ULL,\n    17055089170653843365ULL,\n    11106351196553049343ULL,\n    12200481917389709410ULL,\n    14178332452302952958ULL,\n    10222137491389786854ULL,\n    16402853202701365415ULL,\n    13180175198509732500ULL,\n    1455166639295552768ULL,\n    4979737427905538267ULL,\n    14373133390957169598ULL,\n    4754395508330068134ULL,\n    12693885839888888936ULL,\n    14746109286893937638ULL,\n    8256443300432846081ULL,\n    503470829401805495ULL,\n    18060283403484840865ULL,\n    223111905554076695ULL,\n    9807394769085160210ULL,\n    14524153012332703177ULL,\n    7972467419517226752ULL,\n    9481948494865433649ULL,\n    6421490798076910573ULL,\n    16506009181752747757ULL,\n    6544188641874020091ULL,\n    12431540855806523398ULL,\n    6618245978658207123ULL,\n    1904194992114517540ULL,\n    4690194968617477900ULL,\n    14108188800280108109ULL,\n    15862787664054268077ULL,\n    11567730374266554581ULL,\n    566460245240579986ULL,\n    9696200961293924155ULL,\n    17827591708672731451ULL,\n    12297585812252232655ULL,\n    10918248820356693534ULL,\n    7943078022158449782ULL,\n    11439166569592681111ULL,\n    16953037697774986329ULL,\n    10099035350833820881ULL,\n    5426307685524604809ULL,\n    3038650040793756099ULL,\n    3553537778964778396ULL,\n    12565565344072502413ULL,\n    14615410673750310221ULL,\n    3270511329827310046ULL,\n    2210873698912433630ULL,\n    4336211781059514708ULL,\n    2526856940938685553ULL,\n    15356564490459994656ULL,\n    13305253367388893541ULL,\n    12041457162714389802ULL,\n    17980810537855664424ULL,\n    16183733413348725174ULL,\n    9206290173512442238ULL,\n    1076087784552803205ULL,\n    13541769100454246797ULL,\n    16691089995012038641ULL,\n    8097136923724890918ULL,\n    12338178725452333109ULL,\n    8024306894580752178ULL,\n    10738610715893273420ULL,\n    705310671717682885ULL,\n    10896553797249757487ULL,\n    8548402185506907215ULL,\n    13975419118295597171ULL,\n    10011574200169311036ULL,\n    7327120261626698754ULL,\n    13001361845117582167ULL,\n    14829199722809107024ULL,\n    15866458406886713890ULL,\n    9740995454791413279ULL,\n    15903821788789179873ULL,\n    950498311103338347ULL,\n    11309823363426037284ULL,\n    18164376950504272441ULL,\n    9557806520826308848ULL,\n    6201795305747716642ULL,\n    17776230082235232674ULL,\n    8520000575969616895ULL,\n    8781445267061036756ULL,\n    270538119429159107ULL,\n    5925739443896034094ULL,\n    9299479815492576194ULL,\n    17524856212641035826ULL,\n    1149504369472189142ULL,\n    3249305323221474438ULL,\n    577859395740588557ULL,\n    10374486818177547603ULL,\n    2722648297481868677ULL,\n    14343991397745315524ULL,\n    6374027184725683455ULL,\n    1445767004720311312ULL,\n    18269627072922875919ULL,\n    10330562213031302803ULL,\n    14840495659246433667ULL,\n    9164112642890598023ULL,\n    905890393258871819ULL,\n    804640119687148870ULL,\n    11229379585982321658ULL,\n    17632457556982827740ULL,\n    11163369412005092930ULL,\n    16691530353120304544ULL,\n    14681395382902058857ULL,\n    15418086763916363030ULL,\n    5361599495544208911ULL,\n    2456891913702392502ULL,\n    15104703531100799955ULL,\n    10246138079115047701ULL,\n    10771499922132581514ULL,\n    11910580552051196964ULL,\n    1542036741617809581ULL,\n    10680784062588495303ULL,\n    2134796969204913051ULL,\n    13652765670422105814ULL,\n    3310537599618580283ULL,\n    9556951871404994733ULL,\n    3566352538807433775ULL,\n    1436607380454235721ULL,\n    5753428088816562753ULL,\n    670825264415961892ULL,\n    13949135213803925436ULL,\n    2273524394325797610ULL,\n    2345888976021271491ULL,\n    7364392071496461927ULL,\n    1378818006292379959ULL,\n    3973257568603402925ULL,\n    858112370402714433ULL,\n    10401676489325557336ULL,\n    1442847686498859522ULL,\n    48253065366238439ULL,\n    8507137633570275834ULL,\n    14273498263780863071ULL,\n    668897021147616397ULL,\n    9006159274974020521ULL,\n    391970115979121381ULL,\n    5428839793638170898ULL,\n    15733486159271243115ULL,\n    7578051076398556408ULL,\n    14276469336238872885ULL,\n    4565010008338206411ULL,\n    352482975037856331ULL,\n    8570637326287613791ULL,\n    14440093320447165059ULL,\n    13256954855142342787ULL,\n    5977274645827402825ULL,\n    12814842766738246412ULL,\n    11657799148863215862ULL,\n    3210024351740157418ULL,\n    8205514332702987679ULL,\n    2511486300927130584ULL,\n    3329517204047545754ULL,\n    8208330820783079447ULL,\n    7931954681266035409ULL,\n    4035676213893141113ULL,\n    10700429197004267608ULL,\n    5290765635871577917ULL,\n    12089900829150832754ULL,\n    6866951294288214810ULL,\n    10972007432955324471ULL,\n    11391491267850750618ULL,\n    6642828344251534284ULL,\n    7987689048966149319ULL,\n    17154508030719858176ULL,\n    9638195347144382594ULL,\n    16868501006432224047ULL,\n    15068183064820139480ULL,\n    11672298211180923873ULL,\n    9909613611469682386ULL,\n    6144789793064789625ULL,\n    6596040658930808244ULL,\n    14073765603350552011ULL,\n    801625908059292136ULL,\n    1543419288821605796ULL,\n    7618558260021138933ULL,\n    10317710018521732393ULL,\n    14884045360921915815ULL,\n    249398806962672218ULL,\n    17109400482621063905ULL,\n    15287297623279451806ULL,\n    15922035355180224998ULL,\n    5407580345299905699ULL,\n    4888345365163850603ULL,\n    11401845357898266478ULL,\n    9312229415980681437ULL,\n    14240988549072253043ULL,\n    2449500326371918447ULL,\n    16572900543257442708ULL,\n    18394951499126178644ULL,\n    16615698769896751355ULL,\n    15939776182072819585ULL,\n    6239820921339887109ULL,\n    14106298817871188464ULL,\n    1119534021120220290ULL,\n    10276103830559998049ULL,\n    2768301834634616663ULL,\n    5734016429524869205ULL,\n    7899565703570850370ULL,\n    6401225177640965672ULL,\n    1467787322467172925ULL,\n    8115794165864699575ULL,\n    3232265451663649251ULL,\n    16028194044004521014ULL,\n    752256321266746425ULL,\n    7659909124069369276ULL,\n    7285383152311811937ULL,\n    3542600618127674019ULL,\n    1288605986478417432ULL,\n    7344349918424130132ULL,\n    9015524238925531129ULL,\n    16539369502908468095ULL,\n    2766565367136064679ULL,\n    4392302990477963244ULL,\n    15863984353702454852ULL,\n    13791866813886728963ULL,\n    923590485688113219ULL,\n    17032003830767507451ULL,\n    3641942379860706017ULL,\n    1732048705832596873ULL,\n    12157161182277547843ULL,\n    5582379276417183829ULL,\n    3303177557290278141ULL,\n    11424580034451257541ULL,\n    6852534168831884751ULL,\n    11082472998063359532ULL,\n    4332618493727576221ULL,\n    18260383652885740206ULL,\n    12975018635334178099ULL,\n    2157658476816337610ULL,\n    4668865189510836563ULL,\n    8344845724680500165ULL,\n    11086012763777664897ULL,\n    16808837058134265130ULL,\n    16518852372734015275ULL,\n    8074692171060694622ULL,\n    10106853857940414860ULL,\n    6074900310288870771ULL,\n    11533124005983658392ULL,\n    6791838022926523326ULL,\n    257359508173027083ULL,\n    16547861393292780670ULL,\n    4334494008002184596ULL,\n    11394437127925947894ULL,\n    11484941705111361795ULL,\n    6408200098188122168ULL,\n    7510749929837998206ULL,\n    15018084551576309451ULL,\n    2525072741745223731ULL,\n    16660194758927029988ULL,\n    6494059677009724753ULL,\n    6532620775805873975ULL,\n    2614131097917440984ULL,\n    3982637015137686412ULL,\n    2525137629853604617ULL,\n    2873962527691607365ULL,\n    14205567674539691646ULL,\n    7013578251571401668ULL,\n    317128927781179106ULL,\n    3562024274923344129ULL,\n    2899629716801732627ULL,\n    11464114140357671142ULL,\n    5528996102125106080ULL,\n    15465187977971886225ULL,\n    10797879231554523021ULL,\n    517728393809434994ULL,\n    8355390818157937521ULL,\n    10808933191861522623ULL,\n    3980201697087125198ULL,\n    8068670330842307807ULL,\n    2206034729509733472ULL,\n    16176000828643217616ULL,\n    4107326124050422003ULL,\n    4261302568769914495ULL,\n    6573834983674741731ULL,\n    13183058754590453246ULL,\n    14863609730666696900ULL,\n    5428219860165521118ULL,\n    11078405938774475922ULL,\n    15888403091728211021ULL,\n    1414823218123123584ULL,\n    14449117115146110260ULL,\n    9415342835046915377ULL,\n    10082244795927085927ULL,\n    15321905527953406138ULL,\n    12772869485077058997ULL,\n    14786676736977359788ULL,\n    16366954654950671646ULL,\n    12278935412389620597ULL,\n    2380442439994500936ULL,\n    16744995341707002614ULL,\n    17163218122998542730ULL,\n    9752949850154018131ULL,\n    6725744020675158348ULL,\n    4801976868823386455ULL,\n    16952534183914676666ULL,\n    14851567991973766085ULL,\n    6086792625117696280ULL,\n    10590949811652078026ULL,\n    18321518096411518845ULL,\n    11350072612512648182ULL,\n    13871192325330467452ULL,\n    9162981778612802627ULL,\n    4424859174682681678ULL,\n    6599284937536033860ULL,\n    15484019390316064415ULL,\n    12953848416864586519ULL,\n    517099554370685775ULL,\n    7700207249825981304ULL,\n    6351479021546008736ULL,\n    4014591873230643871ULL,\n    11847399081009018567ULL,\n    559028932686483286ULL,\n    2173777069343479726ULL,\n    7419754410852462129ULL,\n    5863718589994020018ULL,\n    5570315961294345840ULL,\n    11452312027470944883ULL,\n    7678540332443919901ULL,\n    10655672607233785502ULL,\n    13548964556219507811ULL,\n    15703867487097708893ULL,\n    635431802677809170ULL,\n    3511044843439792549ULL,\n    8749939820205758469ULL,\n    16641100851398354379ULL,\n    16579913584853013410ULL,\n    2860000890027061143ULL,\n    5686971079923259932ULL,\n    16704313226797980845ULL,\n    12465533777907440402ULL,\n    16721517972089767726ULL,\n    3081085273849832827ULL,\n    14454246431875833643ULL,\n    13711104064454495754ULL,\n    13556721592383761295ULL,\n    1203917620800027117ULL,\n    6565059229007852048ULL,\n    4091128231014581085ULL,\n    1190503015868505561ULL,\n    13015962807729430070ULL,\n    17425592565615841784ULL,\n    4645200218815941165ULL,\n    8024829510281657164ULL,\n    17523216612475719970ULL,\n    12542739339666551196ULL,\n    9892890286828436137ULL,\n    14459191643501879690ULL,\n    17211797760636115062ULL,\n    2359497679167552654ULL,\n    8912223334063929535ULL,\n    7941508769180229423ULL,\n    6520978387474968530ULL,\n    9177574953959405154ULL,\n    10570524388057156454ULL,\n    9591476736548824451ULL,\n    5365477835702836602ULL,\n    18282708219953439541ULL,\n    10675405260882325529ULL,\n    4111364082651134865ULL,\n    13354971593163146200ULL,\n    15278380379866199474ULL,\n    3916796757054548741ULL,\n    10255301153118473226ULL,\n    11218818017395378451ULL,\n    18199972171316199814ULL,\n    15835192868053046121ULL,\n    17263684841549330914ULL,\n    11062866309293624331ULL,\n    6227321897897629496ULL,\n    353630029907650271ULL,\n    3523400764112538118ULL,\n    7201649299779298743ULL,\n    2395129313921031310ULL,\n    2148285522302009459ULL,\n    8984162342722902626ULL,\n    438870319050497926ULL,\n    4784542304497524290ULL,\n    1535296304186293308ULL,\n    18156227382645116207ULL,\n    9116403414927450509ULL,\n    17682476890377988001ULL,\n    7342041589427523494ULL,\n    10465669611741865418ULL,\n    7587852749592860317ULL,\n    13970362271603846881ULL,\n    12806994729369312160ULL,\n    14576012959627738556ULL,\n    11344411464570745071ULL,\n    16141054063446615314ULL,\n    17417751941543466126ULL,\n    7167537835446755389ULL,\n    8791130914220180596ULL,\n    14508464268795815076ULL,\n    9989025427981015211ULL,\n    2324712211473518453ULL,\n    14393744007810057723ULL,\n    4154667155580299997ULL,\n    14397495111547232209ULL,\n    14614902884941517187ULL,\n    14103608014145752644ULL,\n    14247888300446498473ULL,\n    6625160353677663590ULL,\n    18340761418155558898ULL,\n    8156477033561538569ULL,\n    1420800945002467066ULL,\n    5212269837197149039ULL,\n    10471882435706547080ULL,\n    8012169697907594461ULL,\n    7095733166245235782ULL,\n    11042078796808228086ULL,\n    6168551548506037618ULL,\n    4590970638561285765ULL,\n    4779805325908989585ULL,\n    7237003122666740608ULL,\n    6054886848806457069ULL,\n    6949156210491550655ULL,\n    16285281660892720973ULL,\n    16410054239032241285ULL,\n    3273959370146289803ULL,\n    8470326293357303292ULL,\n    16422997300759042657ULL,\n    11620358142775987774ULL,\n    5029604686597303618ULL,\n    4494702051450930131ULL,\n    10325154349795834533ULL,\n    16997810131993466409ULL,\n    17486259595113526467ULL,\n    10598865875248242901ULL,\n    17902976944493522212ULL,\n    13854078177218706875ULL,\n    17888430304907373152ULL,\n    12685139671565584444ULL,\n    13833128177232553500ULL,\n    9835699045772546575ULL,\n    5668178551297289676ULL,\n    2340134241526441475ULL,\n    753436891757673272ULL,\n    615698389131366316ULL,\n    10156688433854455047ULL,\n    11525289401331658219ULL,\n    4881738137926906800ULL,\n    2058205709817021123ULL,\n    3991120745420612947ULL,\n    7639248091924834630ULL,\n    13202106123103473578ULL,\n    17654881816051191290ULL,\n    17278668556136726594ULL,\n    11570432399481153098ULL,\n    3988389909121239202ULL,\n    17091622881582178462ULL,\n    4646208563474167604ULL,\n    14106451295779125672ULL,\n    10320120576645331914ULL,\n    12352845852427107953ULL,\n    14899884351265122265ULL,\n    11903870881002315385ULL,\n    9325617668189469648ULL,\n    14598981506432428056ULL,\n    10372459402038349496ULL,\n    6156593784171166651ULL,\n    12545625877168818339ULL,\n    7306863242197644549ULL,\n    666678194678414598ULL,\n    989905834432164197ULL,\n    9401909345577780134ULL,\n    11138299130673465283ULL,\n    18091995195105618240ULL,\n    9655077302136646914ULL,\n    16629827344897372840ULL,\n    3277743911245713220ULL,\n    3731260043836443598ULL,\n    12028700701332907689ULL,\n    2755517292365471353ULL,\n    14117484903723218984ULL,\n    8405966141343551147ULL,\n    2460365840816978098ULL,\n    3084698348460621705ULL,\n    16877669134537860219ULL,\n    4692733610775533309ULL,\n    6687894846888649943ULL,\n    18200485782319732931ULL,\n    8115281591836015458ULL,\n    3505776172882505081ULL,\n    9455074786481930389ULL,\n    868562879685071735ULL,\n    13706664967368568336ULL,\n    10919741247013111758ULL,\n    6834630531859180434ULL,\n    4621407577930771075ULL,\n    10618080096334227009ULL,\n    11437746103811309264ULL,\n    15675833321805422940ULL,\n    6075209348719321507ULL,\n    6783084403656000602ULL,\n    5574471127885898972ULL,\n    14489386878401071203ULL,\n    5562663845846095512ULL,\n    6176943955488204952ULL,\n    10108745001547036152ULL,\n    7085798424112915491ULL,\n    17101583329010070816ULL,\n    10265144651245083245ULL,\n    9229795126994901711ULL,\n    13121942059182209427ULL,\n    5380822570802827374ULL,\n    6213420290406371390ULL,\n    16994135099500516479ULL,\n    5311729593068917780ULL,\n    13914474594064683184ULL,\n    10294280742210115419ULL,\n    10676549584223602652ULL,\n    14964626312377022323ULL,\n    4060474717972033032ULL,\n    8655174219459165787ULL,\n    3933760189253469459ULL,\n    1934553336086304369ULL,\n    14999594410372973815ULL,\n    9721720883676649034ULL,\n    17995882330449937817ULL,\n    4754377650026209282ULL,\n    5047861918011100580ULL,\n    7942728043298293248ULL,\n    163571059010590768ULL,\n    18364405827107400850ULL,\n    5764465207084065842ULL,\n    17361856150526161203ULL,\n    2203504364851205526ULL,\n    16639257772366718318ULL,\n    10261892135711281702ULL,\n    5752607922798510416ULL,\n    7483809040402288727ULL,\n    12671556035681699979ULL,\n    3173398710793036878ULL,\n    5608461027089012844ULL,\n    18433194427043711306ULL,\n    4853344608313074695ULL,\n    6960475574329768041ULL,\n    17856758933408630525ULL,\n    10375604847958350097ULL,\n    13241904152391552449ULL,\n    3929901330855494012ULL,\n    4262901104370426306ULL,\n    6789561922862578032ULL,\n    8967186525299355515ULL,\n    4533217413093813275ULL,\n    5643809189292007051ULL,\n    495169116096013531ULL,\n    12789788495048968654ULL,\n    3671592867726928597ULL,\n    11540188521144517538ULL,\n    2542060706027564144ULL,\n    8503537316519248735ULL,\n    4041715278058699385ULL,\n    2371002316211506661ULL,\n    6026778051648044722ULL,\n    16224603993537761311ULL,\n    2715046259776181603ULL,\n    726517895425566240ULL,\n    15808372696473412102ULL,\n    10923341336360680008ULL,\n    14376117752057142530ULL,\n    5794188995003200529ULL,\n    7112388884803681907ULL,\n    14631534284611984966ULL,\n    18140458773366175981ULL,\n    18108017203931876231ULL,\n    16411813322034815824ULL,\n    9425736345428388810ULL,\n    681769034669892768ULL,\n    17302356545317662801ULL,\n    17229061363757438192ULL,\n    7462688934640402880ULL,\n    3873831564485110343ULL,\n    14447655290514797123ULL,\n    3204697688175724303ULL,\n    9017250591306492777ULL,\n    8606967019226274914ULL,\n    18341515903210492860ULL,\n    12448428375904165220ULL,\n    6852846706383550083ULL,\n    14849962227781994194ULL,\n    15695405892567116701ULL,\n    8298709553165075454ULL,\n    2068932383403115112ULL,\n    10282330483807844168ULL,\n    11128666535570799066ULL,\n    1873046192768279226ULL,\n    10398361192866809411ULL,\n    5124744001566603863ULL,\n    5685746340671115365ULL,\n    14218408944825783065ULL,\n    16797706003748462146ULL,\n    17207563922843033620ULL,\n    4982395803794514206ULL,\n    6047267691529793221ULL,\n    9883533531288749712ULL,\n    14216807392798412691ULL,\n    17307295394612055334ULL,\n    6868286489288706449ULL,\n    10635566991257996815ULL,\n    10162335767850313279ULL,\n    6620076281871975852ULL,\n    4026190233492150127ULL,\n    3124297855929917761ULL,\n    7521661794930467711ULL,\n    1066340990602657303ULL,\n    2636494999739001924ULL,\n    12032146588970726383ULL,\n    8279038900559342741ULL,\n    10008892957410689668ULL,\n    14202650502531736817ULL,\n    14854826779434161303ULL,\n    4953853040606446492ULL,\n    5379966367891113702ULL,\n    13015822609736681502ULL,\n    8543523156850098962ULL,\n    14043677148179279456ULL,\n    10559053636038629705ULL,\n    2086308554844071497ULL,\n    15824510410079630867ULL,\n    3227714471131370975ULL,\n    9522342031796734383ULL,\n    16158673861979479153ULL,\n    12038157437040981119ULL,\n    11895166872864069099ULL,\n    1197811376683642228ULL,\n    13287662539885843563ULL,\n    9544872781832099071ULL,\n    4222233824102404110ULL,\n    2133998761859595484ULL,\n    5819494556654210069ULL,\n    14816211808724849418ULL,\n    10176886659237434172ULL,\n    2501085927953676756ULL,\n    6112568572292747443ULL,\n    1220127868369435755ULL,\n    17005803960319347306ULL,\n    16787616116892133575ULL,\n    16207719857604131382ULL,\n    13594948105060827697ULL,\n    6530612604039981252ULL,\n    13977170874341892019ULL,\n    9042970266133777753ULL,\n    11234856800208830769ULL,\n    7767854113364544268ULL,\n    1749330339295278183ULL,\n    14060641551230624404ULL,\n    11352950071080864403ULL,\n    15471811346457819845ULL,\n    6796164306155903940ULL,\n    15303084408201456249ULL,\n    16707985054069450864ULL,\n    15348527471740531690ULL,\n    2054486066928282679ULL,\n    6629789268351040246ULL,\n    4139725410560897941ULL,\n    10789060182933816928ULL,\n    14649618792009204095ULL,\n    9533762887844972792ULL,\n    9055181833376373829ULL,\n    3704143350857419041ULL,\n    13165788964259745440ULL,\n    10504542472085275604ULL,\n    3143544209934004723ULL,\n    16699442099531285552ULL,\n    17386572975779346746ULL,\n    14440354485847513184ULL,\n    1411337828388295592ULL,\n    17586972803408651810ULL,\n    6722710933583173143ULL,\n    11281233424233979571ULL,\n    15838886002743246952ULL,\n    13015085553311968107ULL,\n    15493397730376770996ULL,\n    1422785807327970234ULL,\n    15748561593943897087ULL,\n    4431934102445986024ULL,\n    9978616396639292812ULL,\n    16469013057559674312ULL,\n    7259596544646623366ULL,\n    13957272777987050596ULL,\n    14377859761745729324ULL,\n    9500915974263946146ULL,\n    10958885459561016085ULL,\n    7510088822976649866ULL,\n    14856037375915845631ULL,\n    6657124356498334900ULL,\n    6432585051348612088ULL,\n    7472010280626180191ULL,\n    12650712874408069031ULL,\n    13307771184331469386ULL,\n    3606757056193547838ULL,\n    10753225301487641561ULL,\n    17127960234419583228ULL,\n    12662871163771014089ULL,\n    1361696617464743971ULL,\n    15310290981014831989ULL,\n    5049631916612880907ULL,\n    10291163003812672231ULL,\n    3728459600828211806ULL,\n    7113899679274835967ULL,\n    1381437621560877367ULL,\n    14678876090558848776ULL,\n    8572205611699271354ULL,\n    11656076436850098712ULL,\n    3353107229658858410ULL,\n    14977880680140355898ULL,\n    1154230544477699172ULL,\n    10344611414856468298ULL,\n    7531078612677779707ULL,\n    444408462496519394ULL,\n    12178076787382474551ULL,\n    16676709090730842448ULL,\n    17441308824435700088ULL,\n    5633032710809931202ULL,\n    12159146730893835319ULL,\n    5979397431393513722ULL,\n    7814705992690851118ULL,\n    8161366737147755117ULL,\n    6270401257124382187ULL,\n    17457842949730015190ULL,\n    8869503855610046715ULL,\n    1367321540179147359ULL,\n    3622066133180912371ULL,\n    1047618614043992660ULL,\n    16424646758938930201ULL,\n    12787262320080161173ULL,\n    18177069184701173844ULL,\n    12958402103292464894ULL,\n    2877816285340623922ULL,\n    8074164302424695822ULL,\n    6323961188256795897ULL,\n    1028001267681189916ULL,\n    5714370383653021262ULL,\n    15486923617954395422ULL,\n    4625777142890033834ULL,\n    15990680293261317289ULL,\n    11358668678364618319ULL,\n    8753696579237620639ULL,\n    12183302499610977852ULL,\n    486578702830777799ULL,\n    2159074250987184315ULL,\n    10443546954350278478ULL,\n    2968794296852091770ULL,\n    5373685372805190878ULL,\n    11948078885379084897ULL,\n    12336292153750786897ULL,\n    16807383662567374304ULL,\n    7759300378778574973ULL,\n    4485931103045023657ULL,\n    1544942395645729893ULL,\n    1297496278182191271ULL,\n    10956050669054913639ULL,\n    1769291150319953740ULL,\n    14721008120084954420ULL,\n    14999752571577229826ULL,\n    17820584995130852873ULL,\n    7440651447095946766ULL,\n    9987457956715433125ULL,\n    9889642071555538770ULL,\n    12048632758099208228ULL,\n    15423504582711899700ULL,\n    5552781593205844370ULL,\n    18395650918324890495ULL,\n    9279219951722003324ULL,\n    16685779012383685843ULL,\n    13103667156860588797ULL,\n    5474263352075413784ULL,\n    1802303426534537993ULL,\n    3995963125335748242ULL,\n    16177465519771071462ULL,\n    13679989420549895228ULL,\n    18127744656978625365ULL,\n    7123064711227635813ULL,\n    9747720381598450822ULL,\n    1473299526565814178ULL,\n    1666034491430050154ULL,\n    7704402999060839246ULL,\n    767546777413541285ULL,\n    10005388627283438978ULL,\n    8229157422178385640ULL,\n    2214479335261691753ULL,\n    8224278484242953448ULL,\n    494258966297178159ULL,\n    4969387893030431675ULL,\n    535542922430921712ULL,\n    5937877118203968353ULL,\n    5599462575412553881ULL,\n    15749110530893874790ULL,\n    18353412484654647679ULL,\n    9079979948107713375ULL,\n    14888061354029764539ULL,\n    10016664043768009830ULL,\n    2031817058464698196ULL,\n    5797460757847049188ULL,\n    7872912553647104140ULL,\n    18289069232539508674ULL,\n    5215145459368815265ULL,\n    5864383471831836378ULL,\n    8491208422244948316ULL,\n    7587037325524155271ULL,\n    11949877882217158810ULL,\n    8381598910683988369ULL,\n    3715937119397627817ULL,\n    5377277016270200372ULL,\n    9099558031350088018ULL,\n    367144875670768153ULL,\n    188648165405249319ULL,\n    10289329211846470265ULL,\n    4889529454063682569ULL,\n    6824053921959398429ULL,\n    9134396574212887182ULL,\n    812667774815093473ULL,\n    10040546039401489200ULL,\n    1986760338268682633ULL,\n    7958742571910822443ULL,\n    13687476500541062980ULL,\n    4830191918830935317ULL,\n    9037921931202846380ULL,\n    1531696582914947421ULL,\n    15130306086294689821ULL,\n    10603032535962039417ULL,\n    14467466580454892187ULL,\n    6719806077783739719ULL,\n    304832169538977902ULL,\n    7270826855766565060ULL,\n    2986615780328031733ULL,\n    6479707210440683016ULL,\n    6727141681144320989ULL,\n    16546030572016867954ULL,\n    13718044368284317239ULL,\n    718208230537977406ULL,\n    14017599392756460177ULL,\n    560524405952296676ULL,\n    5637326204055771419ULL,\n    11550317109918646290ULL,\n    8518204115800444519ULL,\n    13892384781543623850ULL,\n    708886165990047519ULL,\n    521856816411877008ULL,\n    12224065831913282030ULL,\n    7100909301345564229ULL,\n    14375129012709244172ULL,\n    8292500769862814408ULL,\n    14597548536564108634ULL,\n    9710027768722536569ULL,\n    2789057975584840068ULL,\n    12208698343893875160ULL,\n    9274181188426044820ULL,\n    2587524840718576616ULL,\n    5283731783253342481ULL,\n    2674321220718462281ULL,\n    9866410392458578854ULL,\n    15258347057534972137ULL,\n    16056419307937022392ULL,\n    17248897535063579233ULL,\n    16693967340197181122ULL,\n    7370802758592081787ULL,\n    18110513780739605330ULL,\n    4182328690942437781ULL,\n    2280530535003795327ULL,\n    4625067236323182685ULL,\n    15039646624295973319ULL,\n    1233105111039262830ULL,\n    1049898734546971236ULL,\n    7438953705203579140ULL,\n    4890034534460494658ULL,\n    3682896501995365299ULL,\n    1287011115747498929ULL,\n    1943857222626688382ULL,\n    14168264380826889554ULL,\n    15936282183338676693ULL,\n    1801363654962061494ULL,\n    11490473960945601728ULL,\n    5494027806580750721ULL,\n    4226755751436839168ULL,\n    12968386155438644777ULL,\n    7624286457384852440ULL,\n    11947602136291652975ULL,\n    4118832019624349464ULL,\n    1261366687163294470ULL,\n    12313505250481360316ULL,\n    13382455321341479390ULL,\n    1693160180553584595ULL,\n    2432453324166190869ULL,\n    6010846956850374986ULL,\n    13569885434517693700ULL,\n    14297911357958435248ULL,\n    916457708689693757ULL,\n    16343255615852500604ULL,\n    17080809607049040174ULL,\n    2344620405691926760ULL,\n    17357412027177565255ULL,\n    18220226561191256701ULL,\n    11079770241377136693ULL,\n    8572299553537558830ULL,\n    15045890847099503773ULL,\n    14864972512233323605ULL,\n    9457012794167444866ULL,\n    17496304225090418576ULL,\n    7167007156644073831ULL,\n    1232518562916969827ULL,\n    15231965457331640472ULL,\n    5697700595045133405ULL,\n    15241798555541989999ULL,\n    14027209396631041100ULL,\n    15120631807759470712ULL,\n    8331724800817204913ULL,\n    15745827131014900309ULL,\n    14281519675380817768ULL,\n    771470964839806020ULL,\n    2177186744775860483ULL,\n    119850466036019855ULL,\n    15051040198205640499ULL,\n    15360149764912089938ULL,\n    6340787344213891088ULL,\n    10347258672759644663ULL,\n    16890381986084016975ULL,\n    14746624262240377603ULL,\n    14953834377077690225ULL,\n    18168099855836039297ULL,\n    6840777819954990667ULL,\n    12240162065902994382ULL,\n    5883854292069091333ULL,\n    9472963121153353366ULL,\n    16701356703380165783ULL,\n    13247064146881705687ULL,\n    6459488422401765837ULL,\n    15296627432046994254ULL,\n    16275445701590908538ULL,\n    18441963025624205421ULL,\n    13918579586859537738ULL,\n    3446987008605642595ULL,\n    17807988251960307333ULL,\n    2822689808379047393ULL,\n    16976957736821297431ULL,\n    3873772909797598787ULL,\n    12383678519331059026ULL,\n    2658988696945629519ULL,\n    17792154146330693620ULL,\n    6949461939865044520ULL,\n    13290656350019450373ULL,\n    16362310026134112967ULL,\n    9313382589157530527ULL,\n    15499936787890509571ULL,\n    13553142720622741706ULL,\n    9637312203984147193ULL,\n    9821959006496886068ULL,\n    14001161369560815590ULL,\n    3613211846255867725ULL,\n    5887497494808230514ULL,\n    15649865485915700198ULL,\n    4051758046024016378ULL,\n    1939918942921679444ULL,\n    10003485229032038884ULL,\n    7330156478314427490ULL,\n    109866288851925579ULL,\n    13096780538080228279ULL,\n    249235540300039238ULL,\n    7308788405859324890ULL,\n    10911913854085679482ULL,\n    11479040470701642191ULL,\n    3283504501904119019ULL,\n    15350519847080756143ULL,\n    12013249357803038388ULL,\n    4929729200845624878ULL,\n    10276438134663857497ULL,\n    9829506236079320901ULL,\n    17535074031916558935ULL,\n    10439025358116919108ULL,\n    5360597719630852057ULL,\n    6095781034833298859ULL,\n    3561753763610714882ULL,\n    14436466097168743024ULL,\n    10202632279755063011ULL,\n    5958698481843734627ULL,\n    359471770145820431ULL,\n    1826881803240972795ULL,\n    2407399441255625035ULL,\n    12606464042774544654ULL,\n    14139007083642608568ULL,\n    9106766577492813516ULL,\n    1738946112944729211ULL,\n    7685584802863232735ULL,\n    11865633484170735390ULL,\n    1242439607532162101ULL,\n    13749864526909002734ULL,\n    1833538451436110733ULL,\n    10617090282479668741ULL,\n    17450744431157176799ULL,\n    10422915670600694510ULL,\n    11444353070626684732ULL,\n    16236369424370065914ULL,\n    4356651211554328663ULL,\n    7538167813800904086ULL,\n    9337339218448769812ULL,\n    16681992071139702958ULL,\n    8239986442678156400ULL,\n    16255387639910107790ULL,\n    2993106237016221406ULL,\n    8425252826859140803ULL,\n    1861840703178678587ULL,\n    9739597023135480731ULL,\n    14726317394379993399ULL,\n    16561961960743295454ULL,\n    14300172422492964432ULL,\n    3576425345824420531ULL,\n    16445859096081052660ULL,\n    14872623052195781100ULL,\n    12454120927638943339ULL,\n    2247824947767592425ULL,\n    7700551132547028956ULL,\n    1170920657933467375ULL,\n    11824927514276724281ULL,\n    7331051236830922220ULL,\n    15717323351549677341ULL,\n    4890809363339690114ULL,\n    3865139405343396495ULL,\n    16057600699038890538ULL,\n    5234374798738346701ULL,\n    2632032810095640441ULL,\n    6013842857594557073ULL,\n    12433471431044669357ULL,\n    14262771896054399489ULL,\n    6446658014718628302ULL,\n    7229558444577096448ULL,\n    13872199742687901116ULL,\n    952395606119921280ULL,\n    1472547022800074667ULL,\n    8500847235576475910ULL,\n    308623728639457857ULL,\n    7831106959981110673ULL,\n    14221846736516958824ULL,\n    5796240453490623563ULL,\n    12981576648874809765ULL,\n    16434266822974705037ULL,\n    14745018226935587220ULL,\n    4034090235427373645ULL,\n    14749224580281562362ULL,\n    14619634180236348185ULL,\n    9818463256038657547ULL,\n    9782515843026543392ULL,\n    8396791438810111190ULL,\n    16104845001429102064ULL,\n    6597335780905594619ULL,\n    8087604609482250757ULL,\n    11337626243478354270ULL,\n    2616078833293994928ULL,\n    6263120375526399459ULL,\n    7324624097771048729ULL,\n    15443556104029229341ULL,\n    865441867960435303ULL,\n    3385276471020662123ULL,\n    16606810265410520019ULL,\n    7733758044026987105ULL,\n    15901646217779410993ULL,\n    1931107594768615801ULL,\n    13595885339956443727ULL,\n    1734226471811708653ULL,\n    11787006789854968365ULL,\n    13473925697040207793ULL,\n    6795191086277285332ULL,\n    5466423536230328879ULL,\n    4942304358314660017ULL,\n    3376417763157001025ULL,\n    9047370796157560970ULL,\n    4265309484812175522ULL,\n    15869425263964541288ULL,\n    10612908973244385058ULL,\n    1765709248820518498ULL,\n    4206621156796187626ULL,\n    14898559392982525110ULL,\n    11537058932194148555ULL,\n    1299512315128822008ULL,\n    14371546888998537039ULL,\n    3857049708503498040ULL,\n    12048162241163569199ULL,\n    2340712079674384862ULL,\n    2179415699324172893ULL,\n    18023918117044002902ULL,\n    4855486034807059661ULL,\n    6087094860859447912ULL,\n    17143718291476367922ULL,\n    1256458344033345896ULL,\n    17579980229608795528ULL,\n    11557093398096277741ULL,\n    1007719678801444034ULL,\n    6779988640333755950ULL,\n    3907362273528096998ULL,\n    14774772624552968330ULL,\n    7544548194281689303ULL,\n    6636615097688206307ULL,\n    15003748247635326837ULL,\n    10104040818681312159ULL,\n    15321864528297940978ULL,\n    17567938070037591442ULL,\n    3856413681424027856ULL,\n    4292783871711268528ULL,\n    16140138884719999023ULL,\n    8478866772430350647ULL,\n    13281328343108678779ULL,\n    171305776311540294ULL,\n    11943708025943116824ULL,\n    6748039362155657697ULL,\n    2183205461918838428ULL,\n    9561785359617558686ULL,\n    3652031999230675404ULL,\n    16321005275195823506ULL,\n    7959930929889385718ULL,\n    17999521541459422754ULL,\n    14635154601438918724ULL,\n    553396447970528490ULL,\n    15862024157345514069ULL,\n    17371777207680026427ULL,\n    3359137361049609599ULL,\n    6943258586876168947ULL,\n    3784445195417718744ULL,\n    2274581329675865992ULL,\n    6275031359059690984ULL,\n    11241808407807133385ULL,\n    3877224075612620234ULL,\n    16870756477494728679ULL,\n    734849408908923633ULL,\n    14240209950286982891ULL,\n    1986420651953859073ULL,\n    11387147591969404274ULL,\n    18077713890942016162ULL,\n    15139168790964589319ULL,\n    6477404551395445767ULL,\n    10436081832179488348ULL,\n    17744304487681668043ULL,\n    2128714340480324937ULL,\n    15859910632245313294ULL,\n    5995483074590576473ULL,\n    6509071106206968172ULL,\n    2163169824462988025ULL,\n    3863527045317748751ULL,\n    8237633082069433530ULL,\n    9841515757799528500ULL,\n    14294777371676485915ULL,\n    11934704548363029679ULL,\n    7120673287036134126ULL,\n    12863291578303148023ULL,\n    16334238291080691817ULL,\n    15014506184267627951ULL,\n    6278562041408864249ULL,\n    15773927532538725983ULL,\n    11862278774206137517ULL,\n    10108495441138000208ULL,\n    3643644710480806625ULL,\n    13183190725774765491ULL,\n    3330440592821674651ULL,\n    11067801741714748965ULL,\n    6289527073896131021ULL,\n    8391475369929113254ULL,\n    12419071995301955387ULL,\n    13941914633954149972ULL,\n    17929047276773532931ULL,\n    14081854430417507654ULL,\n    4122773549016528789ULL,\n    5866939267497024327ULL,\n    5633445301560111924ULL,\n    15148806336003336018ULL,\n    8513300211206524724ULL,\n    1081483082384699582ULL,\n    2252431285306913990ULL,\n    3315840836598307395ULL,\n    8611672253026635924ULL,\n    7692447144052698970ULL,\n    17826945265428611077ULL,\n    4407929597728693553ULL,\n    4129202124902798391ULL,\n    16210351311974513110ULL,\n    7013462431341857666ULL,\n    10679378862009417773ULL,\n    10698731448227451997ULL,\n    10214937890834175264ULL,\n    4867308398708952696ULL,\n    16617172368428362162ULL,\n    17467275560723318293ULL,\n    470191739110710031ULL,\n    3685562506811825781ULL,\n    2905805415767189564ULL,\n    8084502220906609248ULL,\n    11780115290117768470ULL,\n    3336485534708884ULL,\n    474902672695954096ULL,\n    8069687131739545408ULL,\n    12628114540585355261ULL,\n    8107372665444409348ULL,\n    18260745212654035178ULL,\n    13512723775043200645ULL,\n    14293297435359794275ULL,\n    4561193852693787246ULL,\n    3277932672758347822ULL,\n    6239112440980812612ULL,\n    12069158410244739986ULL,\n    12764136687241441664ULL,\n    9103612728729402301ULL,\n    46300154371201744ULL,\n    12608266932428043680ULL,\n    12706319272633005305ULL,\n    8402553077618110420ULL,\n    14275372808014136081ULL,\n    9074687190955529423ULL,\n    3442732534623606404ULL,\n    8699185602383950480ULL,\n    14782484856197519501ULL,\n    1522378370519043561ULL,\n    2734276335299301842ULL,\n    300033226049380445ULL,\n    2807029055204421976ULL,\n    8558096721253991069ULL,\n    14921253645916145844ULL,\n    6449350720490621488ULL,\n    7930533333175007233ULL,\n    14184697264816514386ULL,\n    2326955563868169757ULL,\n    2035352265515334775ULL,\n    8153640834898357023ULL,\n    14984855137181870290ULL,\n    13099153861198352490ULL,\n    13773789017351586912ULL,\n    15200492287234951007ULL,\n    14483739337732840655ULL,\n    6403559166266407540ULL,\n    13753602020720880910ULL,\n    3249279661898332800ULL,\n    9181123651142087069ULL,\n    1836691926200600341ULL,\n    9764040110125927332ULL,\n    6663212195462700590ULL,\n    7109449835495623894ULL,\n    4636864470301046378ULL,\n    13265891422234620013ULL,\n    15463288374108596917ULL,\n    11038262548913554028ULL,\n    18158132467259812090ULL,\n    4815114688002177032ULL,\n    2416784426869281784ULL,\n    6732180818225346150ULL,\n    17393071132099316992ULL,\n    5525092284629074569ULL,\n    1634870171524771550ULL,\n    8292439854432961292ULL,\n    6061222638298614019ULL,\n    15285721643957612773ULL,\n    18283248164276310734ULL,\n    15476901838986528369ULL,\n    6119315299220687605ULL,\n    11702689397173333134ULL,\n    10012597160382659150ULL,\n    13320301609044099934ULL,\n    4681970170429611174ULL,\n    18241817896048179829ULL,\n    16849453277189735160ULL,\n    10049127378905201116ULL,\n    10708164598968648570ULL,\n    15755017209579804185ULL,\n    3752335377410341683ULL,\n    1238205409631919049ULL,\n    1152656054405278355ULL,\n    16558359693635275419ULL,\n    7430194032384888672ULL,\n    543271656567182335ULL,\n    5194796692717647744ULL,\n    5665258811938242506ULL,\n    2300807873645502127ULL,\n    165812044371428982ULL,\n    3939294062048718949ULL,\n    74844995338929047ULL,\n    10052744505958225167ULL,\n    9892303744120031155ULL,\n    15893654591597836243ULL,\n    10459156830982532517ULL,\n    10008127731394723705ULL,\n    14225162161544672503ULL,\n    610469786679517176ULL,\n    6085777220071229420ULL,\n    3968732725768765983ULL,\n    16905520723506218102ULL,\n    9015244267864269380ULL,\n    836844216733147253ULL,\n    15522943025565498336ULL,\n    1227680606736318287ULL,\n    10551664483035308391ULL,\n    8893304960486742702ULL,\n    228633503764621721ULL,\n    8618128597575781798ULL,\n    9389074572030515665ULL,\n    10467270419232710888ULL,\n    2789669318845938730ULL,\n    4540693032004158783ULL,\n    15365997957373073222ULL,\n    14452220300689696232ULL,\n    9611295392598286195ULL,\n    9386731512417660260ULL,\n    13011975161709967294ULL,\n    13887417423927953272ULL,\n    15141666041390241207ULL,\n    13801670979443617350ULL,\n    11912010898649251793ULL,\n    3216755092744419688ULL,\n    7719909053524096732ULL,\n    6570210936025285383ULL,\n    15519528288614946296ULL,\n    5896026138526019560ULL,\n    18241091861601073858ULL,\n    12882790989651701026ULL,\n    2355463796642582687ULL,\n    5034442258850573194ULL,\n    4119189770149100604ULL,\n    1169198788330860086ULL,\n    8990660269277164964ULL,\n    11855231768356149717ULL,\n    4312914019301092679ULL,\n    18133972076692991117ULL,\n    10339301334373249631ULL,\n    6224236857920519326ULL,\n    2145883984422340647ULL,\n    10499737541019029130ULL,\n    9605014900239155921ULL,\n    8774629887936906915ULL,\n    13658562455465335922ULL,\n    2455471276117274492ULL,\n    1599794828360808557ULL,\n    6409710086524383258ULL,\n    13572848057069272691ULL,\n    15859792500999344780ULL,\n    14833852200807153620ULL,\n    18373300168636597590ULL,\n    1647276602047959173ULL,\n    6966105289391190876ULL,\n    16548350976601842266ULL,\n    10244487721105664919ULL,\n    7368498157055229230ULL,\n    1050505464345977270ULL,\n    14642008335963743180ULL,\n    15946168150732473042ULL,\n    9559473511359696133ULL,\n    16892266906861528962ULL,\n    4381326281718415033ULL,\n    18287679639551536447ULL,\n    7073033387009385311ULL,\n    5995412771461993422ULL,\n    17142672245759427204ULL,\n};\n\nconst std::pair<const char *, uint32_t> GameFiles[] = {\n    { \"BOOST.TXT\", 1 },\n    { \"LUA5.TXT\", 1 },\n    { \"ZLIB123.TXT\", 1 },\n    { \"default.xex\", 3 },\n    { \"win32/archives/enemy_data.arc\", 1 },\n    { \"win32/archives/event_data.arc\", 1 },\n    { \"win32/archives/particle_data.arc\", 1 },\n    { \"win32/archives/player_amy.arc\", 1 },\n    { \"win32/archives/player_blaze.arc\", 1 },\n    { \"win32/archives/player_common.arc\", 1 },\n    { \"win32/archives/player_knuckles.arc\", 1 },\n    { \"win32/archives/player_omega.arc\", 1 },\n    { \"win32/archives/player_princess.arc\", 1 },\n    { \"win32/archives/player_rouge.arc\", 1 },\n    { \"win32/archives/player_shadow.arc\", 1 },\n    { \"win32/archives/player_silver.arc\", 1 },\n    { \"win32/archives/player_sonic.arc\", 1 },\n    { \"win32/archives/player_supershadow.arc\", 1 },\n    { \"win32/archives/player_supersilver.arc\", 1 },\n    { \"win32/archives/player_supersonic.arc\", 1 },\n    { \"win32/archives/player_tails.arc\", 1 },\n    { \"win32/archives/radarmap.arc\", 1 },\n    { \"win32/archives/sprite.arc\", 1 },\n    { \"win32/archives/stage_aqa_a.arc\", 1 },\n    { \"win32/archives/stage_aqa_b.arc\", 1 },\n    { \"win32/archives/stage_boss_dr1_dtd.arc\", 1 },\n    { \"win32/archives/stage_boss_dr1_wap.arc\", 1 },\n    { \"win32/archives/stage_boss_dr2.arc\", 1 },\n    { \"win32/archives/stage_boss_dr3.arc\", 1 },\n    { \"win32/archives/stage_boss_iblis02.arc\", 1 },\n    { \"win32/archives/stage_boss_iblis03.arc\", 1 },\n    { \"win32/archives/stage_boss_mefi01.arc\", 1 },\n    { \"win32/archives/stage_boss_mefi02.arc\", 1 },\n    { \"win32/archives/stage_boss_rct.arc\", 1 },\n    { \"win32/archives/stage_boss_solaris.arc\", 1 },\n    { \"win32/archives/stage_csc_a.arc\", 1 },\n    { \"win32/archives/stage_csc_b.arc\", 1 },\n    { \"win32/archives/stage_csc_c.arc\", 1 },\n    { \"win32/archives/stage_csc_e.arc\", 1 },\n    { \"win32/archives/stage_csc_f.arc\", 1 },\n    { \"win32/archives/stage_csc_iblis01.arc\", 1 },\n    { \"win32/archives/stage_dtd_a.arc\", 1 },\n    { \"win32/archives/stage_dtd_b.arc\", 1 },\n    { \"win32/archives/stage_e0003.arc\", 1 },\n    { \"win32/archives/stage_e0009.arc\", 1 },\n    { \"win32/archives/stage_e0010.arc\", 1 },\n    { \"win32/archives/stage_e0012.arc\", 1 },\n    { \"win32/archives/stage_e0021.arc\", 1 },\n    { \"win32/archives/stage_e0022.arc\", 1 },\n    { \"win32/archives/stage_e0023.arc\", 1 },\n    { \"win32/archives/stage_e0026.arc\", 1 },\n    { \"win32/archives/stage_e0028.arc\", 1 },\n    { \"win32/archives/stage_e0031.arc\", 1 },\n    { \"win32/archives/stage_e0104.arc\", 1 },\n    { \"win32/archives/stage_e0105.arc\", 1 },\n    { \"win32/archives/stage_e0106.arc\", 1 },\n    { \"win32/archives/stage_e0120.arc\", 1 },\n    { \"win32/archives/stage_e0125.arc\", 1 },\n    { \"win32/archives/stage_e0206.arc\", 1 },\n    { \"win32/archives/stage_e0214.arc\", 1 },\n    { \"win32/archives/stage_e0216.arc\", 1 },\n    { \"win32/archives/stage_e0221.arc\", 1 },\n    { \"win32/archives/stage_e0304.arc\", 1 },\n    { \"win32/archives/stage_flc_a.arc\", 1 },\n    { \"win32/archives/stage_flc_b.arc\", 1 },\n    { \"win32/archives/stage_kdv_a.arc\", 1 },\n    { \"win32/archives/stage_kdv_b.arc\", 1 },\n    { \"win32/archives/stage_kdv_c.arc\", 1 },\n    { \"win32/archives/stage_kdv_d.arc\", 1 },\n    { \"win32/archives/stage_rct_a.arc\", 1 },\n    { \"win32/archives/stage_rct_b.arc\", 1 },\n    { \"win32/archives/stage_tpj_a.arc\", 1 },\n    { \"win32/archives/stage_tpj_b.arc\", 1 },\n    { \"win32/archives/stage_tpj_c.arc\", 1 },\n    { \"win32/archives/stage_twn_a.arc\", 1 },\n    { \"win32/archives/stage_twn_b.arc\", 1 },\n    { \"win32/archives/stage_twn_c.arc\", 1 },\n    { \"win32/archives/stage_twn_d.arc\", 1 },\n    { \"win32/archives/stage_wap_a.arc\", 1 },\n    { \"win32/archives/stage_wap_b.arc\", 1 },\n    { \"win32/archives/stage_wvo_a.arc\", 1 },\n    { \"win32/archives/stage_wvo_b.arc\", 1 },\n    { \"xenon/archives/cache.arc\", 1 },\n    { \"xenon/archives/enemy.arc\", 1 },\n    { \"xenon/archives/event.arc\", 1 },\n    { \"xenon/archives/game.arc\", 1 },\n    { \"xenon/archives/human.arc\", 1 },\n    { \"xenon/archives/object.arc\", 1 },\n    { \"xenon/archives/particle.arc\", 1 },\n    { \"xenon/archives/player.arc\", 1 },\n    { \"xenon/archives/scripts.arc\", 1 },\n    { \"xenon/archives/shader.arc\", 1 },\n    { \"xenon/archives/shader_lt.arc\", 1 },\n    { \"xenon/archives/sound.arc\", 1 },\n    { \"xenon/archives/stage.arc\", 1 },\n    { \"xenon/archives/system.arc\", 1 },\n    { \"xenon/archives/text.arc\", 1 },\n    { \"xenon/event/e0000/e0000.wmv\", 1 },\n    { \"xenon/event/e0001/E0001_00_TL.xma\", 1 },\n    { \"xenon/event/e0001/E0001_01_SN.xma\", 1 },\n    { \"xenon/event/e0001/E0001_02_TL.xma\", 1 },\n    { \"xenon/event/e0001/E0001_03_TL.xma\", 1 },\n    { \"xenon/event/e0001/E0001_06_TL.xma\", 1 },\n    { \"xenon/event/e0001/E0001_07_TL.xma\", 1 },\n    { \"xenon/event/e0001/E0001_08_SN.xma\", 1 },\n    { \"xenon/event/e0001/E0001_09_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_00_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_01_SN.xma\", 1 },\n    { \"xenon/event/e0001/J0001_02_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_03_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_04_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_06_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_07_TL.xma\", 1 },\n    { \"xenon/event/e0001/J0001_08_SN.xma\", 1 },\n    { \"xenon/event/e0001/J0001_09_TL.xma\", 1 },\n    { \"xenon/event/e0002/E0002_00_TL.xma\", 1 },\n    { \"xenon/event/e0002/E0002_01_TL.xma\", 1 },\n    { \"xenon/event/e0002/J0002_00_TL.xma\", 1 },\n    { \"xenon/event/e0002/J0002_01_TL.xma\", 1 },\n    { \"xenon/event/e0003/E0003_00_SR.xma\", 1 },\n    { \"xenon/event/e0003/E0003_02_EL.xma\", 1 },\n    { \"xenon/event/e0003/E0003_03_SN.xma\", 1 },\n    { \"xenon/event/e0003/E0003_04_EL.xma\", 1 },\n    { \"xenon/event/e0003/E0003_05_SN.xma\", 1 },\n    { \"xenon/event/e0003/E0003_06_TL.xma\", 1 },\n    { \"xenon/event/e0003/E0003_07_EG.xma\", 1 },\n    { \"xenon/event/e0003/E0003_09_EG.xma\", 1 },\n    { \"xenon/event/e0003/E0003_10_SN.xma\", 1 },\n    { \"xenon/event/e0003/E0003_11_TL.xma\", 1 },\n    { \"xenon/event/e0003/J0003_00_SR.xma\", 1 },\n    { \"xenon/event/e0003/J0003_02_EL.xma\", 1 },\n    { \"xenon/event/e0003/J0003_03_SN.xma\", 1 },\n    { \"xenon/event/e0003/J0003_04_EL.xma\", 1 },\n    { \"xenon/event/e0003/J0003_05_SN.xma\", 1 },\n    { \"xenon/event/e0003/J0003_06_TL.xma\", 1 },\n    { \"xenon/event/e0003/J0003_07_EG.xma\", 1 },\n    { \"xenon/event/e0003/J0003_09_EG.xma\", 1 },\n    { \"xenon/event/e0003/J0003_10_SN.xma\", 1 },\n    { \"xenon/event/e0003/J0003_11_TL.xma\", 1 },\n    { \"xenon/event/e0004/E0004_00_SN.xma\", 1 },\n    { \"xenon/event/e0004/E0004_01_TL.xma\", 1 },\n    { \"xenon/event/e0004/E0004_02_TL.xma\", 1 },\n    { \"xenon/event/e0004/E0004_03_SN.xma\", 1 },\n    { \"xenon/event/e0004/E0004_04_TL.xma\", 1 },\n    { \"xenon/event/e0004/E0004_05_SN.xma\", 1 },\n    { \"xenon/event/e0004/E0004_06_EL.xma\", 1 },\n    { \"xenon/event/e0004/j0004_00_sn.xma\", 1 },\n    { \"xenon/event/e0004/j0004_01_tl.xma\", 1 },\n    { \"xenon/event/e0004/j0004_02_tl.xma\", 1 },\n    { \"xenon/event/e0004/j0004_03_sn.xma\", 1 },\n    { \"xenon/event/e0004/j0004_04_tl.xma\", 1 },\n    { \"xenon/event/e0004/j0004_05_sn.xma\", 1 },\n    { \"xenon/event/e0004/j0004_06_el.xma\", 1 },\n    { \"xenon/event/e0005/e0005.wmv\", 1 },\n    { \"xenon/event/e0006/E0006_00_EL.xma\", 1 },\n    { \"xenon/event/e0006/E0006_01_SV.xma\", 1 },\n    { \"xenon/event/e0006/E0006_02_SV.xma\", 1 },\n    { \"xenon/event/e0006/E0006_03_SN.xma\", 1 },\n    { \"xenon/event/e0006/E0006_04_SV.xma\", 1 },\n    { \"xenon/event/e0006/E0006_05_SV.xma\", 1 },\n    { \"xenon/event/e0006/J0006_00_EL.xma\", 1 },\n    { \"xenon/event/e0006/J0006_01_SV.xma\", 1 },\n    { \"xenon/event/e0006/J0006_02_SV.xma\", 1 },\n    { \"xenon/event/e0006/J0006_03_SN.xma\", 1 },\n    { \"xenon/event/e0006/J0006_04_SV.xma\", 1 },\n    { \"xenon/event/e0006/J0006_05_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_00_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_01A_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_01B_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_02_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_03_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_04A_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_04B_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_04_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_06_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_07_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_09_EL.xma\", 1 },\n    { \"xenon/event/e0007/E0007_10_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_11A_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_11C_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_12_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_13_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_14_AM.xma\", 1 },\n    { \"xenon/event/e0007/E0007_15_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_16_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_18_AM.xma\", 1 },\n    { \"xenon/event/e0007/E0007_19_SV.xma\", 1 },\n    { \"xenon/event/e0007/E0007_20_SN.xma\", 1 },\n    { \"xenon/event/e0007/E0007_21_AM.xma\", 1 },\n    { \"xenon/event/e0007/E0026_06_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_00_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_01_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_02_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_03_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_04_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_06_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_07_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_09_EL.xma\", 1 },\n    { \"xenon/event/e0007/J0007_10_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_11_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_12_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_13_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_14_AM.xma\", 1 },\n    { \"xenon/event/e0007/J0007_15_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_16_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_18_AM.xma\", 1 },\n    { \"xenon/event/e0007/J0007_19_SV.xma\", 1 },\n    { \"xenon/event/e0007/J0007_20_SN.xma\", 1 },\n    { \"xenon/event/e0007/J0007_21_AM.xma\", 1 },\n    { \"xenon/event/e0008/e0008.wmv\", 1 },\n    { \"xenon/event/e0009/E0009_00_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_01_KN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_02_SN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_03_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_04_EL.xma\", 1 },\n    { \"xenon/event/e0009/E0009_05_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_06_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_08_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_09_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_10A_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_10B_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_10C_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_11A_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_11B_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_11_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_12_TL.xma\", 1 },\n    { \"xenon/event/e0009/E0009_13_KN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_14_KN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_16_KN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_17A_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_17B_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_18_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_20_EL.xma\", 1 },\n    { \"xenon/event/e0009/E0009_21_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_23_SN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_24_KN.xma\", 1 },\n    { \"xenon/event/e0009/E0009_25_TL.xma\", 1 },\n    { \"xenon/event/e0009/E0009_26_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_27_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_28_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_30_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_31_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_32_EL.xma\", 1 },\n    { \"xenon/event/e0009/E0009_33_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_35_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_37_EG.xma\", 1 },\n    { \"xenon/event/e0009/E0009_38_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_00_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_01_KN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_02_SN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_03_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_04_EL.xma\", 1 },\n    { \"xenon/event/e0009/J0009_05_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_06_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_08_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_10_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_11_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_12_TL.xma\", 1 },\n    { \"xenon/event/e0009/J0009_13_KN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_14_KN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_16_KN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_17A_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_17B_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_18_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_20_EL.xma\", 1 },\n    { \"xenon/event/e0009/J0009_21_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_23_SN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_24_KN.xma\", 1 },\n    { \"xenon/event/e0009/J0009_25_TL.xma\", 1 },\n    { \"xenon/event/e0009/J0009_26_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_27_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_28_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_30_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_31_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_32_EL.xma\", 1 },\n    { \"xenon/event/e0009/J0009_33_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_35_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_37_EG.xma\", 1 },\n    { \"xenon/event/e0009/J0009_38_EG.xma\", 1 },\n    { \"xenon/event/e0010/E0010_00_SN.xma\", 1 },\n    { \"xenon/event/e0010/E0010_03_KN.xma\", 1 },\n    { \"xenon/event/e0010/E0010_04_TL.xma\", 1 },\n    { \"xenon/event/e0010/E0010_05_TL.xma\", 1 },\n    { \"xenon/event/e0010/E0010_07_RG.xma\", 1 },\n    { \"xenon/event/e0010/E0010_08_TL.xma\", 1 },\n    { \"xenon/event/e0010/E0010_09_RG.xma\", 1 },\n    { \"xenon/event/e0010/J0010_00_SN.xma\", 1 },\n    { \"xenon/event/e0010/J0010_01_TL.xma\", 1 },\n    { \"xenon/event/e0010/J0010_02_KN.xma\", 1 },\n    { \"xenon/event/e0010/J0010_03_KN.xma\", 1 },\n    { \"xenon/event/e0010/J0010_04_TL.xma\", 1 },\n    { \"xenon/event/e0010/J0010_05_TL.xma\", 1 },\n    { \"xenon/event/e0010/J0010_07_RG.xma\", 1 },\n    { \"xenon/event/e0010/J0010_08_TL.xma\", 1 },\n    { \"xenon/event/e0010/J0010_09_RG.xma\", 1 },\n    { \"xenon/event/e0011/E0011_00_SH.xma\", 1 },\n    { \"xenon/event/e0011/E0011_01_TL.xma\", 1 },\n    { \"xenon/event/e0011/E0011_02_TL.xma\", 1 },\n    { \"xenon/event/e0011/E0011_03_SN.xma\", 1 },\n    { \"xenon/event/e0011/E0011_04_KN.xma\", 1 },\n    { \"xenon/event/e0011/E0011_05_SH.xma\", 1 },\n    { \"xenon/event/e0011/E0011_06_SN.xma\", 1 },\n    { \"xenon/event/e0011/E0011_07_SH.xma\", 1 },\n    { \"xenon/event/e0011/E0011_08_SN.xma\", 1 },\n    { \"xenon/event/e0011/J0011_00_SH.xma\", 1 },\n    { \"xenon/event/e0011/J0011_01_TL.xma\", 1 },\n    { \"xenon/event/e0011/J0011_02_TL.xma\", 1 },\n    { \"xenon/event/e0011/J0011_03_SN.xma\", 1 },\n    { \"xenon/event/e0011/J0011_04_KN.xma\", 1 },\n    { \"xenon/event/e0011/J0011_05_SH.xma\", 1 },\n    { \"xenon/event/e0011/J0011_06_SN.xma\", 1 },\n    { \"xenon/event/e0011/J0011_07_SH.xma\", 1 },\n    { \"xenon/event/e0011/J0011_08_SN.xma\", 1 },\n    { \"xenon/event/e0012/E0012_00_KN.xma\", 1 },\n    { \"xenon/event/e0012/E0012_01_KN.xma\", 1 },\n    { \"xenon/event/e0012/E0012_02_TL.xma\", 1 },\n    { \"xenon/event/e0012/E0012_04_SV.xma\", 1 },\n    { \"xenon/event/e0012/E0012_05_SV.xma\", 1 },\n    { \"xenon/event/e0012/E0012_06_MF.xma\", 1 },\n    { \"xenon/event/e0012/E0012_07_MF.xma\", 1 },\n    { \"xenon/event/e0012/E0012_08_MF.xma\", 1 },\n    { \"xenon/event/e0012/E0012_09_MF.xma\", 1 },\n    { \"xenon/event/e0012/E0012_11_SV.xma\", 1 },\n    { \"xenon/event/e0012/E0012_12_SV.xma\", 1 },\n    { \"xenon/event/e0012/E0012_13_BZ.xma\", 1 },\n    { \"xenon/event/e0012/E0012_14_MF.xma\", 1 },\n    { \"xenon/event/e0012/E0012_15_KN.xma\", 1 },\n    { \"xenon/event/e0012/E0012_18_TL.xma\", 1 },\n    { \"xenon/event/e0012/E0012_19_TL.xma\", 1 },\n    { \"xenon/event/e0012/E0012_21_TL.xma\", 1 },\n    { \"xenon/event/e0012/E0012_22_TL.xma\", 1 },\n    { \"xenon/event/e0012/E0012_23_SN.xma\", 1 },\n    { \"xenon/event/e0012/E0012_25_TL.xma\", 1 },\n    { \"xenon/event/e0012/J0012_00_KN.xma\", 1 },\n    { \"xenon/event/e0012/J0012_01_KN.xma\", 1 },\n    { \"xenon/event/e0012/J0012_02_TL.xma\", 1 },\n    { \"xenon/event/e0012/J0012_04_SV.xma\", 1 },\n    { \"xenon/event/e0012/J0012_05_SV.xma\", 1 },\n    { \"xenon/event/e0012/J0012_06_MF.xma\", 1 },\n    { \"xenon/event/e0012/J0012_07_MF.xma\", 1 },\n    { \"xenon/event/e0012/J0012_08_MF.xma\", 1 },\n    { \"xenon/event/e0012/J0012_09_MF.xma\", 1 },\n    { \"xenon/event/e0012/J0012_11_SV.xma\", 1 },\n    { \"xenon/event/e0012/J0012_12_SV.xma\", 1 },\n    { \"xenon/event/e0012/J0012_13_BZ.xma\", 1 },\n    { \"xenon/event/e0012/J0012_14_MF.xma\", 1 },\n    { \"xenon/event/e0012/J0012_15_KN.xma\", 1 },\n    { \"xenon/event/e0012/J0012_18_TL.xma\", 1 },\n    { \"xenon/event/e0012/J0012_19_TL.xma\", 1 },\n    { \"xenon/event/e0012/J0012_21_TL.xma\", 1 },\n    { \"xenon/event/e0012/J0012_22_TL.xma\", 1 },\n    { \"xenon/event/e0012/J0012_23_SN.xma\", 1 },\n    { \"xenon/event/e0012/J0012_25_TL.xma\", 1 },\n    { \"xenon/event/e0013/E0013_00_RG.xma\", 1 },\n    { \"xenon/event/e0013/E0013_01_RG.xma\", 1 },\n    { \"xenon/event/e0013/E0013_02A_TL.xma\", 1 },\n    { \"xenon/event/e0013/E0013_02B_TL.xma\", 1 },\n    { \"xenon/event/e0013/E0013_03_RG.xma\", 1 },\n    { \"xenon/event/e0013/E0013_05_RG.xma\", 1 },\n    { \"xenon/event/e0013/E0013_06B_KN.xma\", 1 },\n    { \"xenon/event/e0013/E0013_06_KN.xma\", 1 },\n    { \"xenon/event/e0013/E0013_07_SH.xma\", 1 },\n    { \"xenon/event/e0013/E0013_08_TL.xma\", 1 },\n    { \"xenon/event/e0013/E0013_10_RG.xma\", 1 },\n    { \"xenon/event/e0013/E0013_11_KN.xma\", 1 },\n    { \"xenon/event/e0013/E0013_12_RG.xma\", 1 },\n    { \"xenon/event/e0013/E0013_13_SN.xma\", 1 },\n    { \"xenon/event/e0013/E0013_14_SH.xma\", 1 },\n    { \"xenon/event/e0013/J0013_00_RG.xma\", 1 },\n    { \"xenon/event/e0013/J0013_01_RG.xma\", 1 },\n    { \"xenon/event/e0013/J0013_02A_TL.xma\", 1 },\n    { \"xenon/event/e0013/J0013_02B_TL.xma\", 1 },\n    { \"xenon/event/e0013/J0013_03_RG.xma\", 1 },\n    { \"xenon/event/e0013/J0013_05_RG.xma\", 1 },\n    { \"xenon/event/e0013/J0013_06_KN.xma\", 1 },\n    { \"xenon/event/e0013/J0013_07_SH.xma\", 1 },\n    { \"xenon/event/e0013/J0013_08_TL.xma\", 1 },\n    { \"xenon/event/e0013/J0013_10_RG.xma\", 1 },\n    { \"xenon/event/e0013/J0013_11_KN.xma\", 1 },\n    { \"xenon/event/e0013/J0013_12_RG.xma\", 1 },\n    { \"xenon/event/e0013/J0013_13_SN.xma\", 1 },\n    { \"xenon/event/e0013/J0013_14_SH.xma\", 1 },\n    { \"xenon/event/e0014/E0014_00_RG.xma\", 1 },\n    { \"xenon/event/e0014/E0014_01_SH.xma\", 1 },\n    { \"xenon/event/e0014/E0014_02_RG.xma\", 1 },\n    { \"xenon/event/e0014/E0014_03_RG.xma\", 1 },\n    { \"xenon/event/e0014/J0014_00_RG.xma\", 1 },\n    { \"xenon/event/e0014/J0014_01_SH.xma\", 1 },\n    { \"xenon/event/e0014/J0014_02_RG.xma\", 1 },\n    { \"xenon/event/e0014/J0014_03_RG.xma\", 1 },\n    { \"xenon/event/e0015/E0015_00_SN.xma\", 1 },\n    { \"xenon/event/e0015/J0015_00_SN.xma\", 1 },\n    { \"xenon/event/e0015/J0015_01_SH.xma\", 1 },\n    { \"xenon/event/e0016/E0016_00_TL.xma\", 1 },\n    { \"xenon/event/e0016/E0016_01_KN.xma\", 1 },\n    { \"xenon/event/e0016/E0016_02_TL.xma\", 1 },\n    { \"xenon/event/e0016/E0016_03_SN.xma\", 1 },\n    { \"xenon/event/e0016/J0016_00_TL.xma\", 1 },\n    { \"xenon/event/e0016/J0016_01_KN.xma\", 1 },\n    { \"xenon/event/e0016/J0016_02_TL.xma\", 1 },\n    { \"xenon/event/e0016/J0016_03_SN.xma\", 1 },\n    { \"xenon/event/e0017/E0017_00_EL.xma\", 1 },\n    { \"xenon/event/e0017/E0017_02_SN.xma\", 1 },\n    { \"xenon/event/e0017/E0017_03_SN.xma\", 1 },\n    { \"xenon/event/e0017/E0017_04_SV.xma\", 1 },\n    { \"xenon/event/e0017/E0017_05_SV.xma\", 1 },\n    { \"xenon/event/e0017/E0017_06_EL.xma\", 1 },\n    { \"xenon/event/e0017/E0017_07_SV.xma\", 1 },\n    { \"xenon/event/e0017/E0017_08_SN.xma\", 1 },\n    { \"xenon/event/e0017/E0017_10A_EG.xma\", 1 },\n    { \"xenon/event/e0017/E0017_10B_EG.xma\", 1 },\n    { \"xenon/event/e0017/E0017_11_EL.xma\", 1 },\n    { \"xenon/event/e0017/E0017_12_SN.xma\", 1 },\n    { \"xenon/event/e0017/E0017_14_SN.xma\", 1 },\n    { \"xenon/event/e0017/E0017_16_SV.xma\", 1 },\n    { \"xenon/event/e0017/E0017_17_SV.xma\", 1 },\n    { \"xenon/event/e0017/E0017_18_SV.xma\", 1 },\n    { \"xenon/event/e0017/E0017_19_SH.xma\", 1 },\n    { \"xenon/event/e0017/J0017_00_EL.xma\", 1 },\n    { \"xenon/event/e0017/J0017_02_SN.xma\", 1 },\n    { \"xenon/event/e0017/J0017_03_SN.xma\", 1 },\n    { \"xenon/event/e0017/J0017_04_SV.xma\", 1 },\n    { \"xenon/event/e0017/J0017_05_SV.xma\", 1 },\n    { \"xenon/event/e0017/J0017_06_EL.xma\", 1 },\n    { \"xenon/event/e0017/J0017_09_SN.xma\", 1 },\n    { \"xenon/event/e0017/J0017_10A_EG.xma\", 1 },\n    { \"xenon/event/e0017/J0017_10B_EG.xma\", 1 },\n    { \"xenon/event/e0017/J0017_11_EL.xma\", 1 },\n    { \"xenon/event/e0017/J0017_12_SN.xma\", 1 },\n    { \"xenon/event/e0017/J0017_13_SV.xma\", 1 },\n    { \"xenon/event/e0017/J0017_14_SN.xma\", 1 },\n    { \"xenon/event/e0017/J0017_16_SV.xma\", 1 },\n    { \"xenon/event/e0017/J0017_17_SV.xma\", 1 },\n    { \"xenon/event/e0017/J0017_18_SV.xma\", 1 },\n    { \"xenon/event/e0017/J0017_19_SH.xma\", 1 },\n    { \"xenon/event/e0018/E0018_00_EG.xma\", 1 },\n    { \"xenon/event/e0018/E0018_01_EL.xma\", 1 },\n    { \"xenon/event/e0018/E0018_02_EG.xma\", 1 },\n    { \"xenon/event/e0018/E0018_04_EL.xma\", 1 },\n    { \"xenon/event/e0018/E0018_05_EG.xma\", 1 },\n    { \"xenon/event/e0018/E0018_06_EL.xma\", 1 },\n    { \"xenon/event/e0018/E0018_07_EG.xma\", 1 },\n    { \"xenon/event/e0018/J0018_00_EG.xma\", 1 },\n    { \"xenon/event/e0018/J0018_01_EL.xma\", 1 },\n    { \"xenon/event/e0018/J0018_02_EG.xma\", 1 },\n    { \"xenon/event/e0018/J0018_04_EL.xma\", 1 },\n    { \"xenon/event/e0018/J0018_05_EG.xma\", 1 },\n    { \"xenon/event/e0018/J0018_06_EL.xma\", 1 },\n    { \"xenon/event/e0018/J0018_07_EG.xma\", 1 },\n    { \"xenon/event/e0019/E0019_00_SN.xma\", 1 },\n    { \"xenon/event/e0019/E0019_01_EL.xma\", 1 },\n    { \"xenon/event/e0019/E0019_03_EL.xma\", 1 },\n    { \"xenon/event/e0019/E0019_04_SN.xma\", 1 },\n    { \"xenon/event/e0019/E0019_05_EL.xma\", 1 },\n    { \"xenon/event/e0019/E0019_06_SN.xma\", 1 },\n    { \"xenon/event/e0019/E0019_07_EL.xma\", 1 },\n    { \"xenon/event/e0019/J0019_00_SN.xma\", 1 },\n    { \"xenon/event/e0019/J0019_01_EL.xma\", 1 },\n    { \"xenon/event/e0019/J0019_03_EL.xma\", 1 },\n    { \"xenon/event/e0019/J0019_04_SN.xma\", 1 },\n    { \"xenon/event/e0019/J0019_05_EL.xma\", 1 },\n    { \"xenon/event/e0019/J0019_06_SN.xma\", 1 },\n    { \"xenon/event/e0019/J0019_07_EL.xma\", 1 },\n    { \"xenon/event/e0020/e0020.wmv\", 1 },\n    { \"xenon/event/e0021/E0021_00_EG.xma\", 1 },\n    { \"xenon/event/e0021/E0021_01_EG.xma\", 1 },\n    { \"xenon/event/e0021/E0021_02_EG.xma\", 1 },\n    { \"xenon/event/e0021/E0021_03_EG.xma\", 1 },\n    { \"xenon/event/e0021/E0021_05_MD.xma\", 1 },\n    { \"xenon/event/e0021/E0021_07_EL.xma\", 1 },\n    { \"xenon/event/e0021/E0021_08_MA.xma\", 1 },\n    { \"xenon/event/e0021/E0021_09_EL.xma\", 1 },\n    { \"xenon/event/e0021/E0021_10_EL.xma\", 1 },\n    { \"xenon/event/e0021/E0021_11_NPC.xma\", 1 },\n    { \"xenon/event/e0021/E0021_12_NPC.xma\", 1 },\n    { \"xenon/event/e0021/J0021_00_EG.xma\", 1 },\n    { \"xenon/event/e0021/J0021_01_EG.xma\", 1 },\n    { \"xenon/event/e0021/J0021_02_EG.xma\", 1 },\n    { \"xenon/event/e0021/J0021_03_EG.xma\", 1 },\n    { \"xenon/event/e0021/J0021_05_MD.xma\", 1 },\n    { \"xenon/event/e0021/J0021_07_EL.xma\", 1 },\n    { \"xenon/event/e0021/J0021_08_MA.xma\", 1 },\n    { \"xenon/event/e0021/J0021_09_EL.xma\", 1 },\n    { \"xenon/event/e0021/J0021_10_EL.xma\", 1 },\n    { \"xenon/event/e0021/J0021_11_NPC.xma\", 1 },\n    { \"xenon/event/e0021/J0021_12_NPC.xma\", 1 },\n    { \"xenon/event/e0022/E0022_00_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_01_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_02_EL.xma\", 1 },\n    { \"xenon/event/e0022/E0022_03_EL.xma\", 1 },\n    { \"xenon/event/e0022/E0022_04_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_05_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_07_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_08_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_09_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_10_EL.xma\", 1 },\n    { \"xenon/event/e0022/E0022_11A_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_11B_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_12_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_14_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_15_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_16_EG.xma\", 1 },\n    { \"xenon/event/e0022/E0022_17_COM.xma\", 1 },\n    { \"xenon/event/e0022/E0022_18_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_00_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_01_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_02_EL.xma\", 1 },\n    { \"xenon/event/e0022/J0022_03_EL.xma\", 1 },\n    { \"xenon/event/e0022/J0022_04_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_05_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_07_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_08_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_09_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_10_EL.xma\", 1 },\n    { \"xenon/event/e0022/J0022_11A_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_11B_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_12_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_14_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_15_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_16_EG.xma\", 1 },\n    { \"xenon/event/e0022/J0022_17_COM.xma\", 1 },\n    { \"xenon/event/e0022/J0022_18_EG.xma\", 1 },\n    { \"xenon/event/e0023/E0023_01_SV.xma\", 1 },\n    { \"xenon/event/e0023/E0023_02_SV.xma\", 1 },\n    { \"xenon/event/e0023/E0023_03_SN.xma\", 1 },\n    { \"xenon/event/e0023/E0023_04_SV.xma\", 1 },\n    { \"xenon/event/e0023/E0023_05_SV.xma\", 1 },\n    { \"xenon/event/e0023/J0023_01_SV.xma\", 1 },\n    { \"xenon/event/e0023/J0023_02_SV.xma\", 1 },\n    { \"xenon/event/e0023/J0023_03_SN.xma\", 1 },\n    { \"xenon/event/e0023/J0023_04_SV.xma\", 1 },\n    { \"xenon/event/e0023/J0023_05_SV.xma\", 1 },\n    { \"xenon/event/e0024/E0024_00A_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_00B_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_00C_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_01_EL.xma\", 1 },\n    { \"xenon/event/e0024/E0024_02_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_03A_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_03B_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_03C_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_04_EG.xma\", 1 },\n    { \"xenon/event/e0024/E0024_05_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_00A_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_00B_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_00C_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_02_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_03A_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_03B_EG.xma\", 1 },\n    { \"xenon/event/e0024/J0024_03C_EG.xma\", 1 },\n    { \"xenon/event/e0024/j0024_04_eg.xma\", 1 },\n    { \"xenon/event/e0026/E0026_00_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_01_SV.xma\", 1 },\n    { \"xenon/event/e0026/E0026_02_SV.xma\", 1 },\n    { \"xenon/event/e0026/E0026_03_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_04B_SV.xma\", 1 },\n    { \"xenon/event/e0026/E0026_04_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_06A_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_07_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_09_SV.xma\", 1 },\n    { \"xenon/event/e0026/E0026_10_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_11_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_12_SN.xma\", 1 },\n    { \"xenon/event/e0026/E0026_13_SV.xma\", 1 },\n    { \"xenon/event/e0026/J0026_00_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_01_SV.xma\", 1 },\n    { \"xenon/event/e0026/J0026_02B_SV.xma\", 1 },\n    { \"xenon/event/e0026/J0026_02_SV.xma\", 1 },\n    { \"xenon/event/e0026/J0026_03_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_04_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_04_SV.xma\", 1 },\n    { \"xenon/event/e0026/J0026_06_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_07_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_09_SV.xma\", 1 },\n    { \"xenon/event/e0026/J0026_10_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_11_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_12_SN.xma\", 1 },\n    { \"xenon/event/e0026/J0026_13_SV.xma\", 1 },\n    { \"xenon/event/e0028/E0028_00A_COM.xma\", 1 },\n    { \"xenon/event/e0028/E0028_00B_COM.xma\", 1 },\n    { \"xenon/event/e0028/E0028_00C_COM.xma\", 1 },\n    { \"xenon/event/e0028/E0028_01A_COM.xma\", 1 },\n    { \"xenon/event/e0028/E0028_01B_COM.xma\", 1 },\n    { \"xenon/event/e0028/J0028_00A_COM.xma\", 1 },\n    { \"xenon/event/e0028/J0028_00B_COM.xma\", 1 },\n    { \"xenon/event/e0028/J0028_00C_COM.xma\", 1 },\n    { \"xenon/event/e0028/J0028_01A_COM.xma\", 1 },\n    { \"xenon/event/e0028/J0028_01B_COM.xma\", 1 },\n    { \"xenon/event/e0029/E0029_00_SN.xma\", 1 },\n    { \"xenon/event/e0029/E0029_01_EL.xma\", 1 },\n    { \"xenon/event/e0029/E0029_02_SN.xma\", 1 },\n    { \"xenon/event/e0029/E0029_04A_EG.xma\", 1 },\n    { \"xenon/event/e0029/E0029_04B_EG.xma\", 1 },\n    { \"xenon/event/e0029/J0029_00_SN.xma\", 1 },\n    { \"xenon/event/e0029/J0029_01_EL.xma\", 1 },\n    { \"xenon/event/e0029/J0029_02_SN.xma\", 1 },\n    { \"xenon/event/e0029/J0029_04A_EG.xma\", 1 },\n    { \"xenon/event/e0029/J0029_04B_EG.xma\", 1 },\n    { \"xenon/event/e0030/e0030.wmv\", 1 },\n    { \"xenon/event/e0031/E0000_20_EL.xma\", 1 },\n    { \"xenon/event/e0031/E0000_21_SN.xma\", 1 },\n    { \"xenon/event/e0031/E0000_22_EL.xma\", 1 },\n    { \"xenon/event/e0031/E0000_23_EL.xma\", 1 },\n    { \"xenon/event/e0031/E0000_24_SN.xma\", 1 },\n    { \"xenon/event/e0031/E0000_25_EG.xma\", 1 },\n    { \"xenon/event/e0031/E0000_26_EL.xma\", 1 },\n    { \"xenon/event/e0031/E0000_27_SN.xma\", 1 },\n    { \"xenon/event/e0031/E0000_28_EL.xma\", 1 },\n    { \"xenon/event/e0031/E0000_29_EG.xma\", 1 },\n    { \"xenon/event/e0031/J0000_20_EL.xma\", 1 },\n    { \"xenon/event/e0031/J0000_21_SN.xma\", 1 },\n    { \"xenon/event/e0031/J0000_22_EL.xma\", 1 },\n    { \"xenon/event/e0031/J0000_23_EL.xma\", 1 },\n    { \"xenon/event/e0031/J0000_24_SN.xma\", 1 },\n    { \"xenon/event/e0031/J0000_25_EG.xma\", 1 },\n    { \"xenon/event/e0031/J0000_26_EL.xma\", 1 },\n    { \"xenon/event/e0031/J0000_27_SN.xma\", 1 },\n    { \"xenon/event/e0031/J0000_28_EL.xma\", 1 },\n    { \"xenon/event/e0031/J0000_29_EG.xma\", 1 },\n    { \"xenon/event/e0100/e0100.wmv\", 1 },\n    { \"xenon/event/e0102/E0102_00_RG.xma\", 1 },\n    { \"xenon/event/e0102/E0102_01_SH.xma\", 1 },\n    { \"xenon/event/e0102/E0102_02_RG.xma\", 1 },\n    { \"xenon/event/e0102/E0102_03_RG.xma\", 1 },\n    { \"xenon/event/e0102/E0102_04_RG.xma\", 1 },\n    { \"xenon/event/e0102/E0102_05_SH.xma\", 1 },\n    { \"xenon/event/e0102/J0102_00_RG.xma\", 1 },\n    { \"xenon/event/e0102/J0102_01_SH.xma\", 1 },\n    { \"xenon/event/e0102/J0102_02_RG.xma\", 1 },\n    { \"xenon/event/e0102/J0102_03_RG.xma\", 1 },\n    { \"xenon/event/e0102/J0102_04_RG.xma\", 1 },\n    { \"xenon/event/e0102/J0102_05_SH.xma\", 1 },\n    { \"xenon/event/e0103/E0103_01_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_02_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_03_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_04_SH.xma\", 1 },\n    { \"xenon/event/e0103/E0103_05A_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_05B_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_06_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_07_RG.xma\", 1 },\n    { \"xenon/event/e0103/E0103_09_SH.xma\", 1 },\n    { \"xenon/event/e0103/E0103_10_RG.xma\", 1 },\n    { \"xenon/event/e0103/J0103_01_RG.xma\", 1 },\n    { \"xenon/event/e0103/J0103_02_RG.xma\", 1 },\n    { \"xenon/event/e0103/J0103_03_RG.xma\", 1 },\n    { \"xenon/event/e0103/J0103_04_SH.xma\", 1 },\n    { \"xenon/event/e0103/J0103_05_RG.xma\", 1 },\n    { \"xenon/event/e0103/J0103_06_RG.xma\", 1 },\n    { \"xenon/event/e0103/J0103_09_SH.xma\", 1 },\n    { \"xenon/event/e0103/J0103_10_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_00_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_01_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_02_SH.xma\", 1 },\n    { \"xenon/event/e0104/E0104_03_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_04_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_05_EG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_06_EG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_07_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_08_EG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_09_EG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_10_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_11_EG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_12_SH.xma\", 1 },\n    { \"xenon/event/e0104/E0104_13_EG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_15_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_16_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_19_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_20_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_22_SH.xma\", 1 },\n    { \"xenon/event/e0104/E0104_23_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_24_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_26_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_27_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_28_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_29_MF.xma\", 1 },\n    { \"xenon/event/e0104/E0104_30_RG.xma\", 1 },\n    { \"xenon/event/e0104/E0104_31_SH.xma\", 1 },\n    { \"xenon/event/e0104/J0104_00_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_01_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_02_SH.xma\", 1 },\n    { \"xenon/event/e0104/J0104_03_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_04_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_05_EG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_06_EG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_07_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_08_EG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_09_EG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_10_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_11_EG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_13_EG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_14_SH.xma\", 1 },\n    { \"xenon/event/e0104/J0104_15_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_16_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_19_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_20_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_22_SH.xma\", 1 },\n    { \"xenon/event/e0104/J0104_23_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_24_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_26_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_27_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_28_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_29_MF.xma\", 1 },\n    { \"xenon/event/e0104/J0104_30_RG.xma\", 1 },\n    { \"xenon/event/e0104/J0104_31_SH.xma\", 1 },\n    { \"xenon/event/e0105/E0105_00_RG.xma\", 1 },\n    { \"xenon/event/e0105/E0105_01_SH.xma\", 1 },\n    { \"xenon/event/e0105/E0105_02_RG.xma\", 1 },\n    { \"xenon/event/e0105/E0105_03A_SH.xma\", 1 },\n    { \"xenon/event/e0105/E0105_03B_SH.xma\", 1 },\n    { \"xenon/event/e0105/J0105_00_RG.xma\", 1 },\n    { \"xenon/event/e0105/J0105_01_SH.xma\", 1 },\n    { \"xenon/event/e0105/J0105_02_RG.xma\", 1 },\n    { \"xenon/event/e0105/J0105_03A_SH.xma\", 1 },\n    { \"xenon/event/e0105/J0105_03B_SH.xma\", 1 },\n    { \"xenon/event/e0106/E0106_00_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_01_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_02_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_03_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_04_SH.xma\", 1 },\n    { \"xenon/event/e0106/E0106_07_SH.xma\", 1 },\n    { \"xenon/event/e0106/E0106_08_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_09_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_10_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_11_RG.xma\", 1 },\n    { \"xenon/event/e0106/E0106_12_SH.xma\", 1 },\n    { \"xenon/event/e0106/E0106_13_SH.xma\", 1 },\n    { \"xenon/event/e0106/J0106_00_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_01_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_02_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_03_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_04_SH.xma\", 1 },\n    { \"xenon/event/e0106/J0106_05_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_06_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_07_SH.xma\", 1 },\n    { \"xenon/event/e0106/J0106_08_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_09_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_10_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_11_RG.xma\", 1 },\n    { \"xenon/event/e0106/J0106_12_SH.xma\", 1 },\n    { \"xenon/event/e0106/J0106_13_SH.xma\", 1 },\n    { \"xenon/event/e0109/E0109_00_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_02_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_03_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_05_SH.xma\", 1 },\n    { \"xenon/event/e0109/E0109_06_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_08_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_09_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_10_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_11_RG.xma\", 1 },\n    { \"xenon/event/e0109/E0109_12_SH.xma\", 1 },\n    { \"xenon/event/e0109/J0109_00_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_02_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_03_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_05_SH.xma\", 1 },\n    { \"xenon/event/e0109/J0109_06_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_08_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_09_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_10_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_11_RG.xma\", 1 },\n    { \"xenon/event/e0109/J0109_12_SH.xma\", 1 },\n    { \"xenon/event/e0113/E0113_00_RG.xma\", 1 },\n    { \"xenon/event/e0113/E0113_01_RG.xma\", 1 },\n    { \"xenon/event/e0113/E0113_02_GN.xma\", 1 },\n    { \"xenon/event/e0113/E0113_03_GN.xma\", 1 },\n    { \"xenon/event/e0113/E0113_04_RG.xma\", 1 },\n    { \"xenon/event/e0113/E0113_05_RG.xma\", 1 },\n    { \"xenon/event/e0113/J0113_00_RG.xma\", 1 },\n    { \"xenon/event/e0113/J0113_01_RG.xma\", 1 },\n    { \"xenon/event/e0113/J0113_02_GN.xma\", 1 },\n    { \"xenon/event/e0113/J0113_03_GN.xma\", 1 },\n    { \"xenon/event/e0113/J0113_04_RG.xma\", 1 },\n    { \"xenon/event/e0113/J0113_05_RG.xma\", 1 },\n    { \"xenon/event/e0114/E0114_00_RG.xma\", 1 },\n    { \"xenon/event/e0114/E0114_01_RG.xma\", 1 },\n    { \"xenon/event/e0114/E0114_02_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_03_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_04_RG.xma\", 1 },\n    { \"xenon/event/e0114/E0114_05_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_06_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_07_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_08_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_09A_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_09B_OM.xma\", 1 },\n    { \"xenon/event/e0114/E0114_10_RG.xma\", 1 },\n    { \"xenon/event/e0114/J0114_00_RG.xma\", 1 },\n    { \"xenon/event/e0114/J0114_01_RG.xma\", 1 },\n    { \"xenon/event/e0114/J0114_02_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_03_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_04_RG.xma\", 1 },\n    { \"xenon/event/e0114/J0114_05_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_06_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_07_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_08_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_09A_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_09B_OM.xma\", 1 },\n    { \"xenon/event/e0114/J0114_10_RG.xma\", 1 },\n    { \"xenon/event/e0115/E0115_00_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_01_SH.xma\", 1 },\n    { \"xenon/event/e0115/E0115_02_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_03_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_04_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_05_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_06_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_07_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_09A_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_09B_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_10_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_11_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_12A_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_12B_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_13_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_14_SH.xma\", 1 },\n    { \"xenon/event/e0115/E0115_15_SH.xma\", 1 },\n    { \"xenon/event/e0115/E0115_16_MF.xma\", 1 },\n    { \"xenon/event/e0115/E0115_17_SH.xma\", 1 },\n    { \"xenon/event/e0115/J0115_00_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_01_SH.xma\", 1 },\n    { \"xenon/event/e0115/J0115_02_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_03_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_04_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_05_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_06_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_07_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_09A_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_09B_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_10_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_11_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_12A_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_12B_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_13_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_14_SH.xma\", 1 },\n    { \"xenon/event/e0115/J0115_15_SH.xma\", 1 },\n    { \"xenon/event/e0115/J0115_16_MF.xma\", 1 },\n    { \"xenon/event/e0115/J0115_17_SH.xma\", 1 },\n    { \"xenon/event/e0116/E0116_00_SH.xma\", 1 },\n    { \"xenon/event/e0116/E0116_01_SH.xma\", 1 },\n    { \"xenon/event/e0116/E0116_02_OM.xma\", 1 },\n    { \"xenon/event/e0116/J0116_00_SH.xma\", 1 },\n    { \"xenon/event/e0116/J0116_01_SH.xma\", 1 },\n    { \"xenon/event/e0116/J0116_02_OM.xma\", 1 },\n    { \"xenon/event/e0117/E0117_00A_MF.xma\", 1 },\n    { \"xenon/event/e0117/E0117_01_SH.xma\", 1 },\n    { \"xenon/event/e0117/J0117_00_MF.xma\", 1 },\n    { \"xenon/event/e0117/J0117_01_SH.xma\", 1 },\n    { \"xenon/event/e0118/E0118_01_OM.xma\", 1 },\n    { \"xenon/event/e0118/J0118_01_OM.xma\", 1 },\n    { \"xenon/event/e0119/E0119_00_RG.xma\", 1 },\n    { \"xenon/event/e0119/E0119_02_RG.xma\", 1 },\n    { \"xenon/event/e0119/E0119_04_SH.xma\", 1 },\n    { \"xenon/event/e0119/E0119_05_OM.xma\", 1 },\n    { \"xenon/event/e0119/E0119_06_SH.xma\", 1 },\n    { \"xenon/event/e0119/E0119_07_RG.xma\", 1 },\n    { \"xenon/event/e0119/E0119_08_SH.xma\", 1 },\n    { \"xenon/event/e0119/E0119_09_RG.xma\", 1 },\n    { \"xenon/event/e0119/E0119_10_RG.xma\", 1 },\n    { \"xenon/event/e0119/E0119_11_RG.xma\", 1 },\n    { \"xenon/event/e0119/E0119_12_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_00_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_02_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_04_SH.xma\", 1 },\n    { \"xenon/event/e0119/J0119_05_OM.xma\", 1 },\n    { \"xenon/event/e0119/J0119_06_SH.xma\", 1 },\n    { \"xenon/event/e0119/J0119_07_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_08_SH.xma\", 1 },\n    { \"xenon/event/e0119/J0119_09_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_10_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_11_RG.xma\", 1 },\n    { \"xenon/event/e0119/J0119_12_RG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_00_EG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_01_EG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_02_SH.xma\", 1 },\n    { \"xenon/event/e0120/E0120_03_EG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_04_EG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_05_SH.xma\", 1 },\n    { \"xenon/event/e0120/E0120_06_EG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_07_EG.xma\", 1 },\n    { \"xenon/event/e0120/E0120_08_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_00_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_01_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_02_SH.xma\", 1 },\n    { \"xenon/event/e0120/J0120_03_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_04_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_05_SH.xma\", 1 },\n    { \"xenon/event/e0120/J0120_06_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_07_EG.xma\", 1 },\n    { \"xenon/event/e0120/J0120_08_EG.xma\", 1 },\n    { \"xenon/event/e0122/E0122_00_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_01A_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_01_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_02_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_03_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_04_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_05_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_06_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_07_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_08_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_09_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_10_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_11_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_12_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_13_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_14_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_15_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_16_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_17_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_18_SV.xma\", 1 },\n    { \"xenon/event/e0122/E0122_19_SH.xma\", 1 },\n    { \"xenon/event/e0122/E0122_21_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_00_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_01_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_02_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_03_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_04_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_05_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_06_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_07_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_08_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_09_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_10_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_11_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_12_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_13_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_14_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_15_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_16_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_17_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_18_SV.xma\", 1 },\n    { \"xenon/event/e0122/J0122_19_SH.xma\", 1 },\n    { \"xenon/event/e0122/J0122_21_SH.xma\", 1 },\n    { \"xenon/event/e0123/e0123.wmv\", 1 },\n    { \"xenon/event/e0124/e0124.wmv\", 1 },\n    { \"xenon/event/e0125/E0125_00_SV.xma\", 1 },\n    { \"xenon/event/e0125/E0125_01_SH.xma\", 1 },\n    { \"xenon/event/e0125/E0125_02A_SV.xma\", 1 },\n    { \"xenon/event/e0125/E0125_02B_SV.xma\", 1 },\n    { \"xenon/event/e0125/E0125_03_SV.xma\", 1 },\n    { \"xenon/event/e0125/E0125_04_SH.xma\", 1 },\n    { \"xenon/event/e0125/E0125_05_SH.xma\", 1 },\n    { \"xenon/event/e0125/E0125_06_SV.xma\", 1 },\n    { \"xenon/event/e0125/E0125_07_SH.xma\", 1 },\n    { \"xenon/event/e0125/E0125_08_SV.xma\", 1 },\n    { \"xenon/event/e0125/J0125_00_SV.xma\", 1 },\n    { \"xenon/event/e0125/J0125_01_SH.xma\", 1 },\n    { \"xenon/event/e0125/J0125_02A_SV.xma\", 1 },\n    { \"xenon/event/e0125/J0125_02B_SV.xma\", 1 },\n    { \"xenon/event/e0125/J0125_03_SV.xma\", 1 },\n    { \"xenon/event/e0125/J0125_04_SH.xma\", 1 },\n    { \"xenon/event/e0125/J0125_05_SH.xma\", 1 },\n    { \"xenon/event/e0125/J0125_06_SV.xma\", 1 },\n    { \"xenon/event/e0125/J0125_07_SH.xma\", 1 },\n    { \"xenon/event/e0125/J0125_08_SV.xma\", 1 },\n    { \"xenon/event/e0126/E0126_00_RG.xma\", 1 },\n    { \"xenon/event/e0126/E0126_01_RG.xma\", 1 },\n    { \"xenon/event/e0126/E0126_02_RG.xma\", 1 },\n    { \"xenon/event/e0126/E0126_03_RG.xma\", 1 },\n    { \"xenon/event/e0126/E0126_04_SH.xma\", 1 },\n    { \"xenon/event/e0126/E0126_05_SH.xma\", 1 },\n    { \"xenon/event/e0126/E0126_06A_RG.xma\", 1 },\n    { \"xenon/event/e0126/E0126_06B_RG.xma\", 1 },\n    { \"xenon/event/e0126/E0126_07_SH.xma\", 1 },\n    { \"xenon/event/e0126/J0126_00_RG.xma\", 1 },\n    { \"xenon/event/e0126/J0126_01_RG.xma\", 1 },\n    { \"xenon/event/e0126/J0126_02_RG.xma\", 1 },\n    { \"xenon/event/e0126/J0126_03_RG.xma\", 1 },\n    { \"xenon/event/e0126/J0126_04_SH.xma\", 1 },\n    { \"xenon/event/e0126/J0126_05_SH.xma\", 1 },\n    { \"xenon/event/e0126/J0126_06A_RG.xma\", 1 },\n    { \"xenon/event/e0126/J0126_06B_RG.xma\", 1 },\n    { \"xenon/event/e0126/J0126_07_SH.xma\", 1 },\n    { \"xenon/event/e0127/E0127_00_MF.xma\", 1 },\n    { \"xenon/event/e0127/E0127_01_MF.xma\", 1 },\n    { \"xenon/event/e0127/E0127_02_MF.xma\", 1 },\n    { \"xenon/event/e0127/E0127_03_MF.xma\", 1 },\n    { \"xenon/event/e0127/E0127_04_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_06_OM.xma\", 1 },\n    { \"xenon/event/e0127/E0127_07_OM.xma\", 1 },\n    { \"xenon/event/e0127/E0127_08_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_09_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_10_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_11_OM.xma\", 1 },\n    { \"xenon/event/e0127/E0127_12A_OM.xma\", 1 },\n    { \"xenon/event/e0127/E0127_12B_OM.xma\", 1 },\n    { \"xenon/event/e0127/E0127_13_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_14_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_15_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_16_RG.xma\", 1 },\n    { \"xenon/event/e0127/E0127_17_SH.xma\", 1 },\n    { \"xenon/event/e0127/J0127_00_MF.xma\", 1 },\n    { \"xenon/event/e0127/J0127_01_MF.xma\", 1 },\n    { \"xenon/event/e0127/J0127_02_MF.xma\", 1 },\n    { \"xenon/event/e0127/J0127_03_MF.xma\", 1 },\n    { \"xenon/event/e0127/J0127_04_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_06_OM.xma\", 1 },\n    { \"xenon/event/e0127/J0127_07_OM.xma\", 1 },\n    { \"xenon/event/e0127/J0127_08_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_09_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_10_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_11_OM.xma\", 1 },\n    { \"xenon/event/e0127/J0127_12A_OM.xma\", 1 },\n    { \"xenon/event/e0127/J0127_12B_OM.xma\", 1 },\n    { \"xenon/event/e0127/J0127_13_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_14_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_15_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_16_RG.xma\", 1 },\n    { \"xenon/event/e0127/J0127_17_SH.xma\", 1 },\n    { \"xenon/event/e0128/E0128_00_RG.xma\", 1 },\n    { \"xenon/event/e0128/E0128_01_RG.xma\", 1 },\n    { \"xenon/event/e0128/E0128_02_SH.xma\", 1 },\n    { \"xenon/event/e0128/E0128_03_SH.xma\", 1 },\n    { \"xenon/event/e0128/E0128_05_OM.xma\", 1 },\n    { \"xenon/event/e0128/J0128_00_RG.xma\", 1 },\n    { \"xenon/event/e0128/J0128_01_RG.xma\", 1 },\n    { \"xenon/event/e0128/J0128_02_SH.xma\", 1 },\n    { \"xenon/event/e0128/J0128_03_SH.xma\", 1 },\n    { \"xenon/event/e0128/J0128_05_OM.xma\", 1 },\n    { \"xenon/event/e0129/E0129_00_SH.xma\", 1 },\n    { \"xenon/event/e0129/E0129_01_MF.xma\", 1 },\n    { \"xenon/event/e0129/E0129_02_MF.xma\", 1 },\n    { \"xenon/event/e0129/E0129_03A_MF.xma\", 1 },\n    { \"xenon/event/e0129/E0129_03B_MF.xma\", 1 },\n    { \"xenon/event/e0129/E0129_04_SH.xma\", 1 },\n    { \"xenon/event/e0129/E0129_05_MF.xma\", 1 },\n    { \"xenon/event/e0129/E0129_06_SH.xma\", 1 },\n    { \"xenon/event/e0129/E0129_07_SH.xma\", 1 },\n    { \"xenon/event/e0129/E0129_09A_MF.xma\", 1 },\n    { \"xenon/event/e0129/E0129_09B_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_00_SH.xma\", 1 },\n    { \"xenon/event/e0129/J0129_01_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_02_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_03A_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_03B_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_04_SH.xma\", 1 },\n    { \"xenon/event/e0129/J0129_05_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_06_SH.xma\", 1 },\n    { \"xenon/event/e0129/J0129_07_SH.xma\", 1 },\n    { \"xenon/event/e0129/J0129_09A_MF.xma\", 1 },\n    { \"xenon/event/e0129/J0129_09B_MF.xma\", 1 },\n    { \"xenon/event/e0130/e0130.wmv\", 1 },\n    { \"xenon/event/e0200/e0200.wmv\", 1 },\n    { \"xenon/event/e0201/E0201_00_SV.xma\", 1 },\n    { \"xenon/event/e0201/J0201_00_SV.xma\", 1 },\n    { \"xenon/event/e0202/E0202_00_BZ.xma\", 1 },\n    { \"xenon/event/e0202/E0202_01_SV.xma\", 1 },\n    { \"xenon/event/e0202/E0202_02_SV.xma\", 1 },\n    { \"xenon/event/e0202/E0202_03_BZ.xma\", 1 },\n    { \"xenon/event/e0202/E0202_04_SV.xma\", 1 },\n    { \"xenon/event/e0202/E0202_05_SV.xma\", 1 },\n    { \"xenon/event/e0202/E0202_06_MF.xma\", 1 },\n    { \"xenon/event/e0202/E0202_07_MF.xma\", 1 },\n    { \"xenon/event/e0202/E0202_08_MF.xma\", 1 },\n    { \"xenon/event/e0202/E0202_09_MF.xma\", 1 },\n    { \"xenon/event/e0202/E0202_10_SV.xma\", 1 },\n    { \"xenon/event/e0202/E0202_11_SV.xma\", 1 },\n    { \"xenon/event/e0202/J0202_00_BZ.xma\", 1 },\n    { \"xenon/event/e0202/J0202_01_SV.xma\", 1 },\n    { \"xenon/event/e0202/J0202_02_SV.xma\", 1 },\n    { \"xenon/event/e0202/J0202_03_BZ.xma\", 1 },\n    { \"xenon/event/e0202/J0202_04_SV.xma\", 1 },\n    { \"xenon/event/e0202/J0202_05_SV.xma\", 1 },\n    { \"xenon/event/e0202/J0202_06_MF.xma\", 1 },\n    { \"xenon/event/e0202/J0202_07_MF.xma\", 1 },\n    { \"xenon/event/e0202/J0202_08_MF.xma\", 1 },\n    { \"xenon/event/e0202/J0202_09_MF.xma\", 1 },\n    { \"xenon/event/e0202/J0202_10_SV.xma\", 1 },\n    { \"xenon/event/e0202/J0202_11_SV.xma\", 1 },\n    { \"xenon/event/e0203/E0203_00_MF.xma\", 1 },\n    { \"xenon/event/e0203/E0203_01_SV.xma\", 1 },\n    { \"xenon/event/e0203/E0203_02_MF.xma\", 1 },\n    { \"xenon/event/e0203/E0203_03_SV.xma\", 1 },\n    { \"xenon/event/e0203/E0203_04_MF.xma\", 1 },\n    { \"xenon/event/e0203/E0203_05_MF.xma\", 1 },\n    { \"xenon/event/e0203/E0203_06_MF.xma\", 1 },\n    { \"xenon/event/e0203/J0203_00_MF.xma\", 1 },\n    { \"xenon/event/e0203/J0203_01_SV.xma\", 1 },\n    { \"xenon/event/e0203/J0203_02_MF.xma\", 1 },\n    { \"xenon/event/e0203/J0203_03_SV.xma\", 1 },\n    { \"xenon/event/e0203/J0203_04_MF.xma\", 1 },\n    { \"xenon/event/e0203/J0203_05_MF.xma\", 1 },\n    { \"xenon/event/e0203/J0203_06_MF.xma\", 1 },\n    { \"xenon/event/e0204/E0204_00_SV.xma\", 1 },\n    { \"xenon/event/e0204/E0204_01_SV.xma\", 1 },\n    { \"xenon/event/e0204/E0204_02_SV.xma\", 1 },\n    { \"xenon/event/e0204/E0204_03_SV.xma\", 1 },\n    { \"xenon/event/e0204/E0204_04_SV.xma\", 1 },\n    { \"xenon/event/e0204/J0204_00_SV.xma\", 1 },\n    { \"xenon/event/e0204/J0204_01_SV.xma\", 1 },\n    { \"xenon/event/e0204/J0204_02_SV.xma\", 1 },\n    { \"xenon/event/e0204/J0204_03_SV.xma\", 1 },\n    { \"xenon/event/e0204/J0204_04_SV.xma\", 1 },\n    { \"xenon/event/e0205/E0205_00_BZ.xma\", 1 },\n    { \"xenon/event/e0205/E0205_01_BZ.xma\", 1 },\n    { \"xenon/event/e0205/E0205_02_BZ.xma\", 1 },\n    { \"xenon/event/e0205/J0205_00_BZ.xma\", 1 },\n    { \"xenon/event/e0205/J0205_01_BZ.xma\", 1 },\n    { \"xenon/event/e0205/J0205_02_BZ.xma\", 1 },\n    { \"xenon/event/e0206/E0000_32_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_00_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_01_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_02_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_04_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_05_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_06_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_07A_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_07B_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_08_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_09_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_10_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_11_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_12_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_13_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_14_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_15_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_16_SV.xma\", 1 },\n    { \"xenon/event/e0206/E0206_17_AM.xma\", 1 },\n    { \"xenon/event/e0206/E0206_18_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0000_32_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_00_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_01_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_02_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_03_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_04_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_05_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_06_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_07A_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_07B_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_08_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_09_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_10_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_11_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_12_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_13_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_14_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_15_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_16_SV.xma\", 1 },\n    { \"xenon/event/e0206/J0206_17_AM.xma\", 1 },\n    { \"xenon/event/e0206/J0206_18_SV.xma\", 1 },\n    { \"xenon/event/e0207/E0207_00_AM.xma\", 1 },\n    { \"xenon/event/e0207/E0207_01_AM.xma\", 1 },\n    { \"xenon/event/e0207/E0207_03_SV.xma\", 1 },\n    { \"xenon/event/e0207/E0207_04_AM.xma\", 1 },\n    { \"xenon/event/e0207/E0207_05_AM.xma\", 1 },\n    { \"xenon/event/e0207/E0207_06_SV.xma\", 1 },\n    { \"xenon/event/e0207/E0207_07_AM.xma\", 1 },\n    { \"xenon/event/e0207/E0207_08_SV.xma\", 1 },\n    { \"xenon/event/e0207/E0207_09_AM.xma\", 1 },\n    { \"xenon/event/e0207/E0207_10_SV.xma\", 1 },\n    { \"xenon/event/e0207/E0207_11_SV.xma\", 1 },\n    { \"xenon/event/e0207/J0207_00_AM.xma\", 1 },\n    { \"xenon/event/e0207/J0207_01_AM.xma\", 1 },\n    { \"xenon/event/e0207/J0207_03_SV.xma\", 1 },\n    { \"xenon/event/e0207/J0207_04_AM.xma\", 1 },\n    { \"xenon/event/e0207/J0207_05_AM.xma\", 1 },\n    { \"xenon/event/e0207/J0207_06_SV.xma\", 1 },\n    { \"xenon/event/e0207/J0207_07_AM.xma\", 1 },\n    { \"xenon/event/e0207/J0207_08_SV.xma\", 1 },\n    { \"xenon/event/e0207/J0207_09_AM.xma\", 1 },\n    { \"xenon/event/e0207/J0207_10_SV.xma\", 1 },\n    { \"xenon/event/e0207/J0207_11_SV.xma\", 1 },\n    { \"xenon/event/e0208/E0208_00_AM.xma\", 1 },\n    { \"xenon/event/e0208/E0208_01_AM.xma\", 1 },\n    { \"xenon/event/e0208/E0208_02_SV.xma\", 1 },\n    { \"xenon/event/e0208/J0208_00_AM.xma\", 1 },\n    { \"xenon/event/e0208/J0208_01_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_00_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_01_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_02_SV.xma\", 1 },\n    { \"xenon/event/e0210/E0210_03_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_04_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_05_SV.xma\", 1 },\n    { \"xenon/event/e0210/E0210_06_SV.xma\", 1 },\n    { \"xenon/event/e0210/E0210_07_SV.xma\", 1 },\n    { \"xenon/event/e0210/E0210_08_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_09_AM.xma\", 1 },\n    { \"xenon/event/e0210/E0210_10_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_00_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_01_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_02_SV.xma\", 1 },\n    { \"xenon/event/e0210/J0210_03_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_04_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_05_SV.xma\", 1 },\n    { \"xenon/event/e0210/J0210_06_SV.xma\", 1 },\n    { \"xenon/event/e0210/J0210_07_SV.xma\", 1 },\n    { \"xenon/event/e0210/J0210_08_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_09_AM.xma\", 1 },\n    { \"xenon/event/e0210/J0210_10_AM.xma\", 1 },\n    { \"xenon/event/e0211/E0211_00_BZ.xma\", 1 },\n    { \"xenon/event/e0211/E0211_02_BZ.xma\", 1 },\n    { \"xenon/event/e0211/E0211_03_SV.xma\", 1 },\n    { \"xenon/event/e0211/E0211_04_SV.xma\", 1 },\n    { \"xenon/event/e0211/E0211_05_SV.xma\", 1 },\n    { \"xenon/event/e0211/E0211_06_BZ.xma\", 1 },\n    { \"xenon/event/e0211/E0211_08_BZ.xma\", 1 },\n    { \"xenon/event/e0211/E0211_09_BZ.xma\", 1 },\n    { \"xenon/event/e0211/E0211_11_BZ.xma\", 1 },\n    { \"xenon/event/e0211/E0211_12_SV.xma\", 1 },\n    { \"xenon/event/e0211/E0211_13_SV.xma\", 1 },\n    { \"xenon/event/e0211/J0211_00_BZ.xma\", 1 },\n    { \"xenon/event/e0211/J0211_02_BZ.xma\", 1 },\n    { \"xenon/event/e0211/J0211_03_SV.xma\", 1 },\n    { \"xenon/event/e0211/J0211_04_SV.xma\", 1 },\n    { \"xenon/event/e0211/J0211_05_SV.xma\", 1 },\n    { \"xenon/event/e0211/J0211_06_BZ.xma\", 1 },\n    { \"xenon/event/e0211/J0211_07_SV.xma\", 1 },\n    { \"xenon/event/e0211/J0211_08_BZ.xma\", 1 },\n    { \"xenon/event/e0211/J0211_09_BZ.xma\", 1 },\n    { \"xenon/event/e0211/J0211_11_BZ.xma\", 1 },\n    { \"xenon/event/e0211/J0211_12_SV.xma\", 1 },\n    { \"xenon/event/e0211/J0211_13_SV.xma\", 1 },\n    { \"xenon/event/e0212/E0212_00_SV.xma\", 1 },\n    { \"xenon/event/e0212/E0212_01_SV.xma\", 1 },\n    { \"xenon/event/e0212/J0212_00_SV.xma\", 1 },\n    { \"xenon/event/e0212/J0212_01_SV.xma\", 1 },\n    { \"xenon/event/e0213/E0213_00_BZ.xma\", 1 },\n    { \"xenon/event/e0213/E0213_01_BZ.xma\", 1 },\n    { \"xenon/event/e0213/E0213_02_BZ.xma\", 1 },\n    { \"xenon/event/e0213/E0213_04_SV.xma\", 1 },\n    { \"xenon/event/e0213/J0213_00_BZ.xma\", 1 },\n    { \"xenon/event/e0213/J0213_01_BZ.xma\", 1 },\n    { \"xenon/event/e0213/J0213_02_BZ.xma\", 1 },\n    { \"xenon/event/e0213/J0213_04_SV.xma\", 1 },\n    { \"xenon/event/e0214/E0214_00_AM.xma\", 1 },\n    { \"xenon/event/e0214/E0214_01_AM.xma\", 1 },\n    { \"xenon/event/e0214/E0214_02_EL.xma\", 1 },\n    { \"xenon/event/e0214/E0214_03_COM.xma\", 1 },\n    { \"xenon/event/e0214/E0214_05_COM.xma\", 1 },\n    { \"xenon/event/e0214/E0214_06_AM.xma\", 1 },\n    { \"xenon/event/e0214/E0214_07_EL.xma\", 1 },\n    { \"xenon/event/e0214/J0214_00_AM.xma\", 1 },\n    { \"xenon/event/e0214/J0214_01_AM.xma\", 1 },\n    { \"xenon/event/e0214/J0214_02_EL.xma\", 1 },\n    { \"xenon/event/e0214/J0214_03_COM.xma\", 1 },\n    { \"xenon/event/e0214/J0214_05_COM.xma\", 1 },\n    { \"xenon/event/e0214/J0214_06_AM.xma\", 1 },\n    { \"xenon/event/e0214/J0214_07_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_00_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_01_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_02_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_03_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_05_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_08_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_10_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_12_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_13_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_15_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_16_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_17_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_20_AM.xma\", 1 },\n    { \"xenon/event/e0215/E0215_21_EL.xma\", 1 },\n    { \"xenon/event/e0215/E0215_21_EL_ALT.xma\", 1 },\n    { \"xenon/event/e0215/E0215_22_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_00_EL.xma\", 1 },\n    { \"xenon/event/e0215/J0215_01_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_02_EL.xma\", 1 },\n    { \"xenon/event/e0215/J0215_03_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_05_EL.xma\", 1 },\n    { \"xenon/event/e0215/J0215_08_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_10_EL.xma\", 1 },\n    { \"xenon/event/e0215/J0215_12_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_13_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_15_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_16_EL.xma\", 1 },\n    { \"xenon/event/e0215/J0215_17_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_20_AM.xma\", 1 },\n    { \"xenon/event/e0215/J0215_21_EL.xma\", 1 },\n    { \"xenon/event/e0215/J0215_22_AM.xma\", 1 },\n    { \"xenon/event/e0216/E0216_00_EL.xma\", 1 },\n    { \"xenon/event/e0216/E0216_01_EG.xma\", 1 },\n    { \"xenon/event/e0216/J0216_00_EL.xma\", 1 },\n    { \"xenon/event/e0216/J0216_01_EG.xma\", 1 },\n    { \"xenon/event/e0217/E0217_00_SV.xma\", 1 },\n    { \"xenon/event/e0217/E0217_01_SV.xma\", 1 },\n    { \"xenon/event/e0217/E0217_02_SV.xma\", 1 },\n    { \"xenon/event/e0217/E0217_03_MF.xma\", 1 },\n    { \"xenon/event/e0217/E0217_04_MF.xma\", 1 },\n    { \"xenon/event/e0217/E0217_06_MF.xma\", 1 },\n    { \"xenon/event/e0217/J0217_00_SV.xma\", 1 },\n    { \"xenon/event/e0217/J0217_01_SV.xma\", 1 },\n    { \"xenon/event/e0217/J0217_02_SV.xma\", 1 },\n    { \"xenon/event/e0217/J0217_03_MF.xma\", 1 },\n    { \"xenon/event/e0217/J0217_04_MF.xma\", 1 },\n    { \"xenon/event/e0217/J0217_06_MF.xma\", 1 },\n    { \"xenon/event/e0221/E0221_00_SV.xma\", 1 },\n    { \"xenon/event/e0221/E0221_01_SV.xma\", 1 },\n    { \"xenon/event/e0221/E0221_02_SV.xma\", 1 },\n    { \"xenon/event/e0221/E0221_03_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_04_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_05_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_06_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_07_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_09_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_10_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_12_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_13_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_14_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_16_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_17_SR.xma\", 1 },\n    { \"xenon/event/e0221/E0221_18_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_00_SV.xma\", 1 },\n    { \"xenon/event/e0221/J0221_01_SV.xma\", 1 },\n    { \"xenon/event/e0221/J0221_02_SV.xma\", 1 },\n    { \"xenon/event/e0221/J0221_03_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_04_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_05_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_06_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_07_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_09_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_10_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_12_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_13_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_14_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_16_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_17_SR.xma\", 1 },\n    { \"xenon/event/e0221/J0221_18_SR.xma\", 1 },\n    { \"xenon/event/e0222/E0222_00_EL.xma\", 1 },\n    { \"xenon/event/e0222/E0222_02_SV.xma\", 1 },\n    { \"xenon/event/e0222/J0222_00_EL.xma\", 1 },\n    { \"xenon/event/e0222/J0222_02_SV.xma\", 1 },\n    { \"xenon/event/e0223/E0223_00_SV.xma\", 1 },\n    { \"xenon/event/e0223/E0223_01_BZ.xma\", 1 },\n    { \"xenon/event/e0223/E0223_02_SV.xma\", 1 },\n    { \"xenon/event/e0223/E0223_03_BZ.xma\", 1 },\n    { \"xenon/event/e0223/E0223_04_NPC.xma\", 1 },\n    { \"xenon/event/e0223/E0223_05_SV.xma\", 1 },\n    { \"xenon/event/e0223/E0223_06_BZ.xma\", 1 },\n    { \"xenon/event/e0223/E0223_09_SV.xma\", 1 },\n    { \"xenon/event/e0223/J0223_00_SV.xma\", 1 },\n    { \"xenon/event/e0223/J0223_01_BZ.xma\", 1 },\n    { \"xenon/event/e0223/J0223_02_SV.xma\", 1 },\n    { \"xenon/event/e0223/J0223_03_BZ.xma\", 1 },\n    { \"xenon/event/e0223/J0223_04_NPC.xma\", 1 },\n    { \"xenon/event/e0223/J0223_05_SV.xma\", 1 },\n    { \"xenon/event/e0223/J0223_06_BZ.xma\", 1 },\n    { \"xenon/event/e0223/J0223_08_SV.xma\", 1 },\n    { \"xenon/event/e0223/J0223_09_SV.xma\", 1 },\n    { \"xenon/event/e0223/J0223_10_BZ.xma\", 1 },\n    { \"xenon/event/e0226/E0226_00A_SV.xma\", 1 },\n    { \"xenon/event/e0226/E0226_00B_SV.xma\", 1 },\n    { \"xenon/event/e0226/E0226_01_BZ.xma\", 1 },\n    { \"xenon/event/e0226/E0226_02_BZ.xma\", 1 },\n    { \"xenon/event/e0226/J0226_00A_SV.xma\", 1 },\n    { \"xenon/event/e0226/J0226_00B_SV.xma\", 1 },\n    { \"xenon/event/e0226/J0226_01_BZ.xma\", 1 },\n    { \"xenon/event/e0226/J0226_02_BZ.xma\", 1 },\n    { \"xenon/event/e0227/E0227_00_SV.xma\", 1 },\n    { \"xenon/event/e0227/E0227_01_SV.xma\", 1 },\n    { \"xenon/event/e0227/J0227_00_SV.xma\", 1 },\n    { \"xenon/event/e0227/J0227_01_SV.xma\", 1 },\n    { \"xenon/event/e0228/e0228.wmv\", 1 },\n    { \"xenon/event/e0301/E0301_00_EL.xma\", 1 },\n    { \"xenon/event/e0301/E0301_01_SN.xma\", 1 },\n    { \"xenon/event/e0301/E0301_02_EL.xma\", 1 },\n    { \"xenon/event/e0301/E0301_03_EL.xma\", 1 },\n    { \"xenon/event/e0301/E0301_04_MF.xma\", 1 },\n    { \"xenon/event/e0301/E0301_05_EL.xma\", 1 },\n    { \"xenon/event/e0301/E0301_06_SR.xma\", 1 },\n    { \"xenon/event/e0301/E0301_07_SR.xma\", 1 },\n    { \"xenon/event/e0301/E0301_08_EL.xma\", 1 },\n    { \"xenon/event/e0301/E0301_09_EL.xma\", 1 },\n    { \"xenon/event/e0301/E0301_10_MF.xma\", 1 },\n    { \"xenon/event/e0301/E0301_11_MF.xma\", 1 },\n    { \"xenon/event/e0301/E0301_12_MF.xma\", 1 },\n    { \"xenon/event/e0301/E0301_13_MF.xma\", 1 },\n    { \"xenon/event/e0301/E0301_14_MF.xma\", 1 },\n    { \"xenon/event/e0301/J0301_00_EL.xma\", 1 },\n    { \"xenon/event/e0301/J0301_01_SN.xma\", 1 },\n    { \"xenon/event/e0301/J0301_02_EL.xma\", 1 },\n    { \"xenon/event/e0301/J0301_03_EL.xma\", 1 },\n    { \"xenon/event/e0301/J0301_04_MF.xma\", 1 },\n    { \"xenon/event/e0301/J0301_05_EL.xma\", 1 },\n    { \"xenon/event/e0301/J0301_06_SR.xma\", 1 },\n    { \"xenon/event/e0301/J0301_07_SR.xma\", 1 },\n    { \"xenon/event/e0301/J0301_08_EL.xma\", 1 },\n    { \"xenon/event/e0301/J0301_09_EL.xma\", 1 },\n    { \"xenon/event/e0301/J0301_10_MF.xma\", 1 },\n    { \"xenon/event/e0301/J0301_11_MF.xma\", 1 },\n    { \"xenon/event/e0301/J0301_12_MF.xma\", 1 },\n    { \"xenon/event/e0301/J0301_13_MF.xma\", 1 },\n    { \"xenon/event/e0301/J0301_14_MF.xma\", 1 },\n    { \"xenon/event/e0303/e0303.wmv\", 1 },\n    { \"xenon/event/e0304/E0304_00_TL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_01_AM.xma\", 1 },\n    { \"xenon/event/e0304/E0304_02_AM.xma\", 1 },\n    { \"xenon/event/e0304/E0304_03_KN.xma\", 1 },\n    { \"xenon/event/e0304/E0304_04_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_05_AM.xma\", 1 },\n    { \"xenon/event/e0304/E0304_06_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_07_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_08_RG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_09_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_11_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_12_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_13_AM.xma\", 1 },\n    { \"xenon/event/e0304/E0304_14_TL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_15_KN.xma\", 1 },\n    { \"xenon/event/e0304/E0304_16_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_18_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_19_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_20_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_22_SH.xma\", 1 },\n    { \"xenon/event/e0304/E0304_23_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_24_KN.xma\", 1 },\n    { \"xenon/event/e0304/E0304_25_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_26_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_27_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_28_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_30A_SH.xma\", 1 },\n    { \"xenon/event/e0304/E0304_30B_SH.xma\", 1 },\n    { \"xenon/event/e0304/E0304_31_SH.xma\", 1 },\n    { \"xenon/event/e0304/E0304_32_AM.xma\", 1 },\n    { \"xenon/event/e0304/E0304_33_EL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_34_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_35_EL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_36_EL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_37_EL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_38_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_39_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_40_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_41_EL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_42_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_43_SV.xma\", 1 },\n    { \"xenon/event/e0304/E0304_44_EL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_45_EG.xma\", 1 },\n    { \"xenon/event/e0304/E0304_46_TL.xma\", 1 },\n    { \"xenon/event/e0304/E0304_47_AM.xma\", 1 },\n    { \"xenon/event/e0304/E0304_48_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_00_TL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_01_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_02_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_03_KN.xma\", 1 },\n    { \"xenon/event/e0304/J0304_04_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_05_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_06_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_07_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_08_RG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_09_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_11_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_12_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_13_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_14_TL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_15_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_15_KN.xma\", 1 },\n    { \"xenon/event/e0304/J0304_16_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_17_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_18_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_19_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_20_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_22_SH.xma\", 1 },\n    { \"xenon/event/e0304/J0304_23_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_24_KN.xma\", 1 },\n    { \"xenon/event/e0304/J0304_25_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_26_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_27_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_28_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_30A_SH.xma\", 1 },\n    { \"xenon/event/e0304/J0304_30B_SH.xma\", 1 },\n    { \"xenon/event/e0304/J0304_31_SH.xma\", 1 },\n    { \"xenon/event/e0304/J0304_32_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_33_EL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_34_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_35_EL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_36_EL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_37_EL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_38_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_39_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_40_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_41_EL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_42_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_43_SV.xma\", 1 },\n    { \"xenon/event/e0304/J0304_44_EL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_45_EG.xma\", 1 },\n    { \"xenon/event/e0304/J0304_46_TL.xma\", 1 },\n    { \"xenon/event/e0304/J0304_47_AM.xma\", 1 },\n    { \"xenon/event/e0304/J0304_48_AM.xma\", 1 },\n    { \"xenon/event/e0305/e0305.wmv\", 1 },\n    { \"xenon/event/e0306/e0306.wmv\", 1 },\n    { \"xenon/event/e0307/e0307.wmv\", 1 },\n    { \"xenon/event/e0308/e0308.wmv\", 1 },\n    { \"xenon/event/e0309/e0309.wmv\", 1 },\n    { \"xenon/event/eboss/e_bos04_e01_mf.xma\", 1 },\n    { \"xenon/event/eboss/e_bos04_e13_mf.xma\", 1 },\n    { \"xenon/event/eboss/e_bos05_e03_mf.xma\", 1 },\n    { \"xenon/event/eboss/e_bos05_e08_mf.xma\", 1 },\n    { \"xenon/event/eboss/e_bos06_e00_eg.xma\", 1 },\n    { \"xenon/event/eboss/e_bos06_e03_eg.xma\", 1 },\n    { \"xenon/event/eboss/e_bos07_e00_eg.xma\", 1 },\n    { \"xenon/event/eboss/e_bos08_e00_1_eg.xma\", 1 },\n    { \"xenon/event/eboss/e_bos08_e00_eg.xma\", 1 },\n    { \"xenon/event/eboss/e_bos08_e16_eg.xma\", 1 },\n    { \"xenon/event/eboss/e_bos09_e00_sn.xma\", 1 },\n    { \"xenon/event/eboss/e_bos09_e00_sv.xma\", 1 },\n    { \"xenon/event/eboss/e_bos10_e00_sd.xma\", 1 },\n    { \"xenon/event/eboss/e_bos10_e00_sv.xma\", 1 },\n    { \"xenon/event/eboss/e_bos11_e06_sd.xma\", 1 },\n    { \"xenon/event/eboss/e_bos11_e06_sn.xma\", 1 },\n    { \"xenon/event/eboss/e_bos11_e06_sv.xma\", 1 },\n    { \"xenon/event/eboss/j_bos04_e01_mf.xma\", 1 },\n    { \"xenon/event/eboss/j_bos04_e13_mf.xma\", 1 },\n    { \"xenon/event/eboss/j_bos05_e03_mf.xma\", 1 },\n    { \"xenon/event/eboss/j_bos05_e08_mf.xma\", 1 },\n    { \"xenon/event/eboss/j_bos06_e00_eg.xma\", 1 },\n    { \"xenon/event/eboss/j_bos06_e03_eg.xma\", 1 },\n    { \"xenon/event/eboss/j_bos07_e00_eg.xma\", 1 },\n    { \"xenon/event/eboss/j_bos08_e00_1_eg.xma\", 1 },\n    { \"xenon/event/eboss/j_bos08_e00_eg.xma\", 1 },\n    { \"xenon/event/eboss/j_bos08_e16_eg.xma\", 1 },\n    { \"xenon/event/eboss/j_bos09_e00_sn.xma\", 1 },\n    { \"xenon/event/eboss/j_bos09_e00_sv.xma\", 1 },\n    { \"xenon/event/eboss/j_bos10_e00_sd.xma\", 1 },\n    { \"xenon/event/eboss/j_bos10_e00_sv.xma\", 1 },\n    { \"xenon/event/eboss/j_bos11_e06_sd.xma\", 1 },\n    { \"xenon/event/eboss/j_bos11_e06_sn.xma\", 1 },\n    { \"xenon/event/eboss/j_bos11_e06_sv.xma\", 1 },\n    { \"xenon/sound/HD_SEGA.wmv\", 1 },\n    { \"xenon/sound/boss_cerberus.xma\", 1 },\n    { \"xenon/sound/boss_character.xma\", 1 },\n    { \"xenon/sound/boss_iblis.xma\", 1 },\n    { \"xenon/sound/boss_iblis3.xma\", 1 },\n    { \"xenon/sound/boss_mefires1.xma\", 1 },\n    { \"xenon/sound/boss_mefires3.xma\", 1 },\n    { \"xenon/sound/boss_solaris1.xma\", 1 },\n    { \"xenon/sound/boss_solaris2.xma\", 1 },\n    { \"xenon/sound/boss_wyvern.xma\", 1 },\n    { \"xenon/sound/end_elise.wmv\", 1 },\n    { \"xenon/sound/end_shadow.wmv\", 1 },\n    { \"xenon/sound/end_silver.wmv\", 1 },\n    { \"xenon/sound/end_sonic.wmv\", 1 },\n    { \"xenon/sound/event/e0001.xma\", 1 },\n    { \"xenon/sound/event/e0003.xma\", 1 },\n    { \"xenon/sound/event/e0006.xma\", 1 },\n    { \"xenon/sound/event/e0007.xma\", 1 },\n    { \"xenon/sound/event/e0009.xma\", 1 },\n    { \"xenon/sound/event/e0011.xma\", 1 },\n    { \"xenon/sound/event/e0012.xma\", 1 },\n    { \"xenon/sound/event/e0014.xma\", 1 },\n    { \"xenon/sound/event/e0016.xma\", 1 },\n    { \"xenon/sound/event/e0017.xma\", 1 },\n    { \"xenon/sound/event/e0018.xma\", 1 },\n    { \"xenon/sound/event/e0021.xma\", 1 },\n    { \"xenon/sound/event/e0022.xma\", 1 },\n    { \"xenon/sound/event/e0024.xma\", 1 },\n    { \"xenon/sound/event/e0026.xma\", 1 },\n    { \"xenon/sound/event/e0028.xma\", 1 },\n    { \"xenon/sound/event/e0029.xma\", 1 },\n    { \"xenon/sound/event/e0031.xma\", 1 },\n    { \"xenon/sound/event/e0103.xma\", 1 },\n    { \"xenon/sound/event/e0104.xma\", 1 },\n    { \"xenon/sound/event/e0106.xma\", 1 },\n    { \"xenon/sound/event/e0112.xma\", 1 },\n    { \"xenon/sound/event/e0114.xma\", 1 },\n    { \"xenon/sound/event/e0115.xma\", 1 },\n    { \"xenon/sound/event/e0122.xma\", 1 },\n    { \"xenon/sound/event/e0125.xma\", 1 },\n    { \"xenon/sound/event/e0126.xma\", 1 },\n    { \"xenon/sound/event/e0127.xma\", 1 },\n    { \"xenon/sound/event/e0129.xma\", 1 },\n    { \"xenon/sound/event/e0201.xma\", 1 },\n    { \"xenon/sound/event/e0202.xma\", 1 },\n    { \"xenon/sound/event/e0203.xma\", 1 },\n    { \"xenon/sound/event/e0208.xma\", 1 },\n    { \"xenon/sound/event/e0209.xma\", 1 },\n    { \"xenon/sound/event/e0210.xma\", 1 },\n    { \"xenon/sound/event/e0211.xma\", 1 },\n    { \"xenon/sound/event/e0214.xma\", 1 },\n    { \"xenon/sound/event/e0215.xma\", 1 },\n    { \"xenon/sound/event/e0221.xma\", 1 },\n    { \"xenon/sound/event/e0222.xma\", 1 },\n    { \"xenon/sound/event/e0226.xma\", 1 },\n    { \"xenon/sound/event/e0227.xma\", 1 },\n    { \"xenon/sound/event/e0300.xma\", 1 },\n    { \"xenon/sound/event/e0301.xma\", 1 },\n    { \"xenon/sound/event/e0304.xma\", 1 },\n    { \"xenon/sound/event/e0304_0.xma\", 1 },\n    { \"xenon/sound/event/e0304_11940.xma\", 1 },\n    { \"xenon/sound/event/e0304_3195.xma\", 1 },\n    { \"xenon/sound/event/e0304_9120.xma\", 1 },\n    { \"xenon/sound/extra.xma\", 1 },\n    { \"xenon/sound/menu.xma\", 1 },\n    { \"xenon/sound/result.xma\", 1 },\n    { \"xenon/sound/roundclear.xma\", 1 },\n    { \"xenon/sound/select.xma\", 1 },\n    { \"xenon/sound/shadow_theme.xma\", 1 },\n    { \"xenon/sound/silver_theme.xma\", 1 },\n    { \"xenon/sound/speed_up.xma\", 1 },\n    { \"xenon/sound/stg_aqa_a.xma\", 1 },\n    { \"xenon/sound/stg_aqa_b.xma\", 1 },\n    { \"xenon/sound/stg_csc_a.xma\", 1 },\n    { \"xenon/sound/stg_csc_b.xma\", 1 },\n    { \"xenon/sound/stg_csc_e.xma\", 1 },\n    { \"xenon/sound/stg_csc_f.xma\", 1 },\n    { \"xenon/sound/stg_dtd_a.xma\", 1 },\n    { \"xenon/sound/stg_dtd_b.xma\", 1 },\n    { \"xenon/sound/stg_end_a.xma\", 1 },\n    { \"xenon/sound/stg_end_b.xma\", 1 },\n    { \"xenon/sound/stg_end_c.xma\", 1 },\n    { \"xenon/sound/stg_end_d.xma\", 1 },\n    { \"xenon/sound/stg_end_e.xma\", 1 },\n    { \"xenon/sound/stg_end_f.xma\", 1 },\n    { \"xenon/sound/stg_end_g.xma\", 1 },\n    { \"xenon/sound/stg_flc_a.xma\", 1 },\n    { \"xenon/sound/stg_flc_b.xma\", 1 },\n    { \"xenon/sound/stg_kdv_a.xma\", 1 },\n    { \"xenon/sound/stg_kdv_b.xma\", 1 },\n    { \"xenon/sound/stg_kdv_c.xma\", 1 },\n    { \"xenon/sound/stg_kdv_d.xma\", 1 },\n    { \"xenon/sound/stg_rct_a.xma\", 1 },\n    { \"xenon/sound/stg_rct_b.xma\", 1 },\n    { \"xenon/sound/stg_tpj_a.xma\", 1 },\n    { \"xenon/sound/stg_tpj_b.xma\", 1 },\n    { \"xenon/sound/stg_tpj_c.xma\", 1 },\n    { \"xenon/sound/stg_twn_a.xma\", 1 },\n    { \"xenon/sound/stg_twn_b.xma\", 1 },\n    { \"xenon/sound/stg_twn_c.xma\", 1 },\n    { \"xenon/sound/stg_twn_shop.xma\", 1 },\n    { \"xenon/sound/stg_wap_a.xma\", 1 },\n    { \"xenon/sound/stg_wap_b.xma\", 1 },\n    { \"xenon/sound/stg_wvo_a.xma\", 1 },\n    { \"xenon/sound/stg_wvo_b.xma\", 1 },\n    { \"xenon/sound/sweetdream.xma\", 1 },\n    { \"xenon/sound/sweetsweetsweet.xma\", 1 },\n    { \"xenon/sound/theme_elise.xma\", 1 },\n    { \"xenon/sound/theme_shadow.xma\", 1 },\n    { \"xenon/sound/theme_silver.xma\", 1 },\n    { \"xenon/sound/theme_sonic.xma\", 1 },\n    { \"xenon/sound/title_loop.xma\", 1 },\n    { \"xenon/sound/title_loop_GBn.wmv\", 1 },\n    { \"xenon/sound/twn_accordion.xma\", 1 },\n    { \"xenon/sound/twn_clear.xma\", 1 },\n    { \"xenon/sound/twn_mission_comical.xma\", 1 },\n    { \"xenon/sound/twn_mission_fast.xma\", 1 },\n    { \"xenon/sound/twn_mission_slow.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e06_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e08_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e09_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v12_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v13_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v14_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v15_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_v16_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all01_w01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all02_a00ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a01ps_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a01ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_a01ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h00ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h01ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h02ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h03ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h04ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h05_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h05ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h06_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h06ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h07_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h07ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h08_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h08ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h09_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h09ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h10_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h10ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h11_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h11ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h12_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h12ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h13_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h13ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h14_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h14ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h15_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h15ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h16_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h16ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h17_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h17ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h18_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h18ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h19_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h19ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h20_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h20ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h21_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h21ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h22_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h22ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h23_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h23ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h24_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h24ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h25_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h25ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h26_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h26ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h27_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h27ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h28_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h28ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h29_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h29ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h30_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h30ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h31_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h31ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h32_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h32ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h33_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h33ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h34_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h34ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h35_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h35ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h36_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h37_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all03_h38_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a07_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a08_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a09_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a10_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a11_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_a12_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all04_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a05_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a06_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a06_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a06_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a07_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a08_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a08_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a08_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a08_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a09_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a09_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a10_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a11_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a12_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a13_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a14_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a15_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a15_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a15_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a15_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a15_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a15_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a16_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a16_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a16_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a17_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a18_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a18_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a18_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a18_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a18_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a18_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a19_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a19_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a19_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a20_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a21_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a21_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a21_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a21_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a21_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a21_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a22_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a22_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a22_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a23_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a23_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a23_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a23_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a23_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a23_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a24_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a24_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a24_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a25_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a25_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a25_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a25_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a25_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a25_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a26_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a26_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a26_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a27_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a28_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a28_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a28_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a28_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a28_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a28_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a29_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a29_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a29_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a30_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a31_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a32_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_a33_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all05_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a03ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a03ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a04ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a04ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a05ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a05ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a07ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a09ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a11ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a12ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a13_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a14_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a14ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a15_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a15ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a16_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a16ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a17_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a18_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a19_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a19ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a20_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a20ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a21_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a21ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a22_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all06_a22ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_am.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/all07_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e00_1_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e10_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e13_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e13_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e14_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e14_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e15_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e15_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e16_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e17_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e18_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e19_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e20_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_e21_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/all08_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa00_e00_1_so.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa00_e00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa00_e01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a06_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a06_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e06_ec.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e07_ec.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e08_ec.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_e09_ec.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/aqa01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/bat01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a04ps_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_a08_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos01_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a05_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos02_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a07_1_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a08_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a09_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos03_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a01_1_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a01_1ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a01ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a05_1ps_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a05_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a05ps_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a06_1ps_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a06_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a06ps_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e01_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e02_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e03_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e04_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e05_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e06_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e07_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e08_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e09_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e10_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e11_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e12_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos04_e13_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e03_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e04_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e05_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e06_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e07_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos05_e08_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a03_1_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a03_1_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_a05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e00_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e02_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e03_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos06_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a02_1_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a05_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_a06_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e02_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e04_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos07_e07_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a04_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a06_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_a07_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e00_1_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e00_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e02_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e03_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e04_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e06_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e07_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e08_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e09_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e10_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e11_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e12_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e13_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e13_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e14_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e15_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e16_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos08_e17_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos09_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos10_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a06_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a07_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a07_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a08_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a09_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a10_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a11_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a12_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a13_1_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a13_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a15_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a16_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a17_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a18_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a19_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a20_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a20ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a21_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a21ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a22_om.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_a23_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e00_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e01_1_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e01_2_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e01_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e03ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e04_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e04ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e05ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e07ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e07ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e07ps_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e10_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e10_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e11_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e12_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/bos11_e14_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_a11_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e06_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/csc01_w03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a03_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e02_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e05_am.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e05_om.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e06_am.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/dtd01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_am.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_om.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_am.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_om.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_h00_1_so.xma\", 1 },\n    { \"xenon/sound/voice/e/end01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a040_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/flc01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e07_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e09_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e10_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_w02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_w02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/kdv01_w02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e06_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e06_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e07_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e08_am.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e09_am.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e09_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e10_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e13_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e14_om.xma\", 1 },\n    { \"xenon/sound/voice/e/mis01_e15_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a10_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu_a.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu_b.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu_c.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu_d.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu_e.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_hu_f.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e06_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e07_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e08_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e09_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_e10_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_ho1_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_ho2_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_ho3_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_hoo_so.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_w00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/rct01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/sys01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a03_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a04_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a05_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a10_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a11_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a12_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a12_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a13_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a13_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_a13_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e03_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e05_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e07_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e09_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e12_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e13_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e14_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e15_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e16_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e17_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e18_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e19_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_e20_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w00_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w01_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w02_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/tpj01_w02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_om.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_a06_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e01ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e04_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e06_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e07ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e08_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e09_pr.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e13_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e14_an.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e15_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e16_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e17_an.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e18_kn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e18_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e18_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e19_1_an.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e19_an.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e20_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e21_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e22_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e23_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e24_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e25_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e26_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e27_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e28_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e29_am.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e30_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e31_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e32_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e33_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e34_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e35_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e36_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e37_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e38_an.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e39_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e40_an.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e41_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e42_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e43_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e44_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e44ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e45_1_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e45_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e46_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e47_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e48_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e49_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e50_1_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e50_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e51_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e52_gn.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_e53_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h05_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h06_so.xma\", 1 },\n    { \"xenon/sound/voice/e/twn01_h07_so.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a03ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a03ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e05_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e06_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e08_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e09_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e10_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e10_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e11_1_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_e11_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/e/wap01_w01_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e05_mf.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e08_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e10_eg.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e10_tl.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e12_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_e13_sn.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_h00ps_so.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_w00_om.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_w00_rg.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/e/wvo01_w00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e06_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e08_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e09_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v09_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v10_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v11_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v12_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v13_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v14_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v15_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_v16_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all01_w01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all02_a00ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h00ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h01ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h02ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h03ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h04ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h05_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h05ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h06_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h06ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h07_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h07ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h08_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h08ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h09_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h09ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h10_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h10ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h11_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h11ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h12_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h12ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h13_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h13ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h14_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h14ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h15_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h15ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h16_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h16ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h17_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h17ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h18_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h18ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h19_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h19ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h20_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h20ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h21_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h21ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h22_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h22ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h23_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h23ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h24_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h24ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h25_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h25ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h26_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h26ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h27_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h27ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h28_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h28ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h29_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h29ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h30_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h30ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h31_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h31ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h32_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h32ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h33_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h33ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h34_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h34ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h35_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h35ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h36_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h37_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all03_h38_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a07_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a08_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a09_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a10_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a11_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_a12_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all04_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a05_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a06_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a06_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a06_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a07_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a08_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a08_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a08_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a08_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a09_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a09_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a10_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a11_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a12_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a13_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a14_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a15_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a15_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a15_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a15_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a15_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a15_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a16_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a16_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a16_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a17_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a18_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a18_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a18_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a18_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a18_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a18_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a19_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a19_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a19_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a20_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a21_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a21_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a21_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a21_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a21_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a21_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a22_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a22_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a22_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a23_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a23_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a23_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a23_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a23_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a23_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a24_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a24_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a24_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a25_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a25_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a25_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a25_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a25_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a25_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a26_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a26_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a26_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a27_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a28_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a28_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a28_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a28_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a28_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a28_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a29_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a29_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a29_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a30_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a31_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a32_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_a33_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all05_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a03ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a03ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a04ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a04ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a05ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a05ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a07ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a09ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a12ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a13_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a14_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a14ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a15_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a15ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a16_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a16ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a17_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a18_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a19_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a19ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a20_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a20ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a21_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a21ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a22_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all06_a22ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_am.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/all07_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e00_1_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e13_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e13_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e14_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e14_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e15_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e15_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e16_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e17_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e18_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e19_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e20_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_e21_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/all08_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa00_e00_1_so.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa00_e00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa00_e01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a06_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a06_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e06_ec.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e07_ec.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e08_ec.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_e09_ec.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/aqa01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/bat01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a04ps_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_a08_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos01_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a05_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos02_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a07_1_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a08_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a09_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_e02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos03_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a01_1_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a01_1ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a01ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a05_1_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a05_1ps_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a05_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a05ps_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a06_1_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a06_1ps_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a06_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a06ps_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_a08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e01_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e02_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e03_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e04_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e05_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e06_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e07_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e08_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e09_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e10_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e11_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e12_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos04_e13_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e03_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e04_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e05_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e06_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e07_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos05_e08_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a03_1_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a03_1_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_a05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e00_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e02_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e03_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos06_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a02_1_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a05_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_a06_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e02_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e04_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos07_e07_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a04_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a06_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_a07_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e00_1_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e00_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e02_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e03_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e04_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e06_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e07_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e08_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e09_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e10_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e11_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e12_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e13_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e13_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e14_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e15_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e16_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos08_e17_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos09_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos10_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a06_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a07_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a07_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a08_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a09_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a10_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a11_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a12_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a13_1_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a13_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a14_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a15_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a16_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a17_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a18_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a19_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a20_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a21_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a21ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a22_om.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_a23_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e00_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e01_1_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e01_2_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e01_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e03ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e04_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e04ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e05ps_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e07ps_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e07ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e07ps_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e10_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e11_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/bos11_e12_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a10_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_a11_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e06_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/csc01_w03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a03_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e02_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e05_am.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e05_om.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e06_am.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_e06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/dtd01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_am.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_om.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_am.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_om.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/end01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/flc01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e07_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e08_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e09_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e10_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_w02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_w02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/kdv01_w02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e06_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e06_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e07_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e08_am.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e09_am.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e09_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e10_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e13_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e14_om.xma\", 1 },\n    { \"xenon/sound/voice/j/mis01_e15_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a07_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_a12_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu_a.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu_b.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu_c.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu_d.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu_e.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_hu_f.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e05_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e06_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e07_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e08_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e09_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_e10_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_w00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/rct01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/sys01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a03_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a04_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a05_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a07_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a09_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a10_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a11_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a12_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a12_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a13_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a13_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_a13_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e03_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e05_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e07_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e08_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e09_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e12_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e13_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e14_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e15_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e16_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e17_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e18_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e19_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_e20_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_w00_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_w01_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_w02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/tpj01_w02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_om.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_a06_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e01ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e02_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e04_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e05_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e06_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e07ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e08_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e09_pr.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e10_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e13_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e14_an.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e15_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e16_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e17_an.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e18_kn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e18_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e18_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e19_1_an.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e19_an.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e20_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e21_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e22_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e23_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e24_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e25_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e26_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e27_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e28_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e29_am.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e30_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e31_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e32_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e33_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e34_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e35_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e36_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e37_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e38_an.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e39_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e40_an.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e41_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e42_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e43_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e44_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e44ps_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e45_1_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e45_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e46_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e47_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e48_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e49_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e50_1_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e50_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e51_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e52_gn.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_e53_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h02_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h03_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h04_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h05_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h06_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h07_1_so.xma\", 1 },\n    { \"xenon/sound/voice/j/twn01_h07_so.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a02_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a03ps_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a03ps_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_a04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e01_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e02_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e03_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e04_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e05_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e06_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e08_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e09_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e10_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e10_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e11_1_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e11_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_e11_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_h01_so.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w01_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w01_sv.xma\", 1 },\n    { \"xenon/sound/voice/j/wap01_w01_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_a00_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_a01_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e00_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e01_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e02_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e03_om.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e03_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e03_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e04_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e05_mf.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e05_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e06_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e07_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e07_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e07_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e08_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e08_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e09_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e09_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e10_eg.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e10_tl.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e11_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e12_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e12_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_e13_sn.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_h00_so.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_h00ps_so.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_w00_bz.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_w00_om.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_w00_rg.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_w00_sd.xma\", 1 },\n    { \"xenon/sound/voice/j/wvo01_w00_tl.xma\", 1 },\n};\n\nconst size_t GameFilesSize = std::size(GameFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/game.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t GameHashes[];\nextern const std::pair<const char *, uint32_t> GameFiles[];\nextern const size_t GameFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/mission_shadow.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t MissionShadowHashes[];\nextern const std::pair<const char *, uint32_t> MissionShadowFiles[];\nextern const size_t MissionShadowFilesSize;\n\nconst uint64_t MissionShadowHashes[] = {\n    14477816605833199221ULL,\n};\n\nconst std::pair<const char *, uint32_t> MissionShadowFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t MissionShadowFilesSize = std::size(MissionShadowFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/mission_shadow.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t MissionShadowHashes[];\nextern const std::pair<const char *, uint32_t> MissionShadowFiles[];\nextern const size_t MissionShadowFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/mission_silver.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t MissionSilverHashes[];\nextern const std::pair<const char *, uint32_t> MissionSilverFiles[];\nextern const size_t MissionSilverFilesSize;\n\nconst uint64_t MissionSilverHashes[] = {\n    7936137245680593367ULL,\n};\n\nconst std::pair<const char *, uint32_t> MissionSilverFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t MissionSilverFilesSize = std::size(MissionSilverFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/mission_silver.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t MissionSilverHashes[];\nextern const std::pair<const char *, uint32_t> MissionSilverFiles[];\nextern const size_t MissionSilverFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/mission_sonic.cpp",
    "content": "// File automatically generated by fshasher\n\n#include <utility>\n\nextern const uint64_t MissionSonicHashes[];\nextern const std::pair<const char *, uint32_t> MissionSonicFiles[];\nextern const size_t MissionSonicFilesSize;\n\nconst uint64_t MissionSonicHashes[] = {\n    11577193563383386051ULL,\n};\n\nconst std::pair<const char *, uint32_t> MissionSonicFiles[] = {\n    { \"download.arc\", 1 },\n};\n\nconst size_t MissionSonicFilesSize = std::size(MissionSonicFiles);\n"
  },
  {
    "path": "MarathonRecomp/install/hashes/mission_sonic.h",
    "content": "// File automatically generated by fshasher\n\n#pragma once\n\n#include <utility>\n\nextern const uint64_t MissionSonicHashes[];\nextern const std::pair<const char *, uint32_t> MissionSonicFiles[];\nextern const size_t MissionSonicFilesSize;\n\n"
  },
  {
    "path": "MarathonRecomp/install/installer.cpp",
    "content": "#include \"installer.h\"\n\n#include <xxh3.h>\n\n#include \"directory_file_system.h\"\n#include \"iso_file_system.h\"\n#include \"xcontent_file_system.h\"\n\n#include \"hashes/episode_sonic.h\"\n#include \"hashes/episode_shadow.h\"\n#include \"hashes/episode_silver.h\"\n#include \"hashes/episode_amigo.h\"\n#include \"hashes/mission_sonic.h\"\n#include \"hashes/mission_shadow.h\"\n#include \"hashes/mission_silver.h\"\n#include \"hashes/game.h\"\n\nstatic const std::string GameDirectory = \"game\";\nstatic const std::string DLCDirectory = \"dlc\";\nstatic const std::string EpisodeSonicDirectory = DLCDirectory + \"/Additional Episode - Sonic Boss Attack\";\nstatic const std::string EpisodeShadowDirectory = DLCDirectory + \"/Additional Episode - Shadow Boss Attack\";\nstatic const std::string EpisodeSilverDirectory = DLCDirectory + \"/Additional Episode - Silver Boss Attack\";\nstatic const std::string EpisodeAmigoDirectory = DLCDirectory + \"/Additional Episode - Team Attack Amigo\";\nstatic const std::string MissionSonicDirectory = DLCDirectory + \"/Additional Mission Pack - Sonic Very Hard\";\nstatic const std::string MissionShadowDirectory = DLCDirectory + \"/Additional Mission Pack - Shadow Very Hard\";\nstatic const std::string MissionSilverDirectory = DLCDirectory + \"/Additional Mission Pack - Silver Very Hard\";\nstatic const std::string GameExecutableFile = \"default.xex\";\nstatic const std::string DLCValidationFile = \"download.arc\";\nstatic const std::string ISOExtension = \".iso\";\n\nstatic std::string fromU8(const std::u8string &str)\n{\n    return std::string(str.begin(), str.end());\n}\n\nstatic std::string fromPath(const std::filesystem::path &path)\n{\n    return fromU8(path.u8string());\n}\n\nstatic std::string toLower(std::string str) {\n    std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); });\n    return str;\n};\n\nstatic std::unique_ptr<VirtualFileSystem> createFileSystemFromPath(const std::filesystem::path &path)\n{\n    if (XContentFileSystem::check(path))\n    {\n        return XContentFileSystem::create(path);\n    }\n    else if (toLower(fromPath(path.extension())) == ISOExtension)\n    {\n        return ISOFileSystem::create(path);\n    }\n    else if (std::filesystem::is_directory(path))\n    {\n        return DirectoryFileSystem::create(path);\n    }\n    else\n    {\n        return nullptr;\n    }\n}\n\nstatic bool checkFile(const FilePair &pair, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, std::vector<uint8_t> &fileData, Journal &journal, const std::function<bool()> &progressCallback, bool checkSizeOnly) {\n    const std::string fileName(pair.first);\n    const uint32_t hashCount = pair.second;\n    const std::filesystem::path filePath = targetDirectory / fileName;\n    if (!std::filesystem::exists(filePath))\n    {\n        journal.lastResult = Journal::Result::FileMissing;\n        journal.lastErrorMessage = fmt::format(\"File \\\"{}\\\" does not exist.\", fileName);\n        return false;\n    }\n\n    std::error_code ec;\n    size_t fileSize = std::filesystem::file_size(filePath, ec);\n    if (ec)\n    {\n        journal.lastResult = Journal::Result::FileReadFailed;\n        journal.lastErrorMessage = fmt::format(\"Failed to read file size for \\\"{}\\\".\", fileName);\n        return false;\n    }\n\n    if (checkSizeOnly)\n    {\n        journal.progressTotal += fileSize;\n    }\n    else\n    {\n        std::ifstream fileStream(filePath, std::ios::binary);\n        if (fileStream.is_open())\n        {\n            fileData.resize(fileSize);\n            fileStream.read((char *)(fileData.data()), fileSize);\n        }\n\n        if (!fileStream.is_open() || fileStream.bad())\n        {\n            journal.lastResult = Journal::Result::FileReadFailed;\n            journal.lastErrorMessage = fmt::format(\"Failed to read file \\\"{}\\\".\", fileName);\n            return false;\n        }\n\n        uint64_t fileHash = XXH3_64bits(fileData.data(), fileSize);\n        bool fileHashFound = false;\n        for (uint32_t i = 0; i < hashCount && !fileHashFound; i++)\n        {\n            fileHashFound = fileHash == fileHashes[i];\n        }\n\n        if (!fileHashFound)\n        {\n            journal.lastResult = Journal::Result::FileHashFailed;\n            journal.lastErrorMessage = fmt::format(\"File \\\"{}\\\" did not match any of the known hashes.\", fileName);\n            return false;\n        }\n\n        journal.progressCounter += fileSize;\n    }\n\n    if (!progressCallback())\n    {\n        journal.lastResult = Journal::Result::Cancelled;\n        journal.lastErrorMessage = \"Check was cancelled.\";\n        return false;\n    }\n\n    return true;\n}\n\nstatic bool copyFile(const FilePair &pair, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, bool skipHashChecks, std::vector<uint8_t> &fileData, Journal &journal, const std::function<bool()> &progressCallback) {\n    const std::string filename(pair.first);\n    const uint32_t hashCount = pair.second;\n    if (!sourceVfs.exists(filename))\n    {\n        journal.lastResult = Journal::Result::FileMissing;\n        journal.lastErrorMessage = fmt::format(\"File \\\"{}\\\" does not exist in \\\"{}\\\".\", filename, sourceVfs.getName());\n        return false;\n    }\n\n    if (!sourceVfs.load(filename, fileData))\n    {\n        journal.lastResult = Journal::Result::FileReadFailed;\n        journal.lastErrorMessage = fmt::format(\"Failed to read file \\\"{}\\\" from \\\"{}\\\".\", filename, sourceVfs.getName());\n        return false;\n    }\n\n    if (!skipHashChecks)\n    {\n        uint64_t fileHash = XXH3_64bits(fileData.data(), fileData.size());\n        bool fileHashFound = false;\n        for (uint32_t i = 0; i < hashCount && !fileHashFound; i++)\n        {\n            fileHashFound = fileHash == fileHashes[i];\n        }\n\n        if (!fileHashFound)\n        {\n            journal.lastResult = Journal::Result::FileHashFailed;\n            journal.lastErrorMessage = fmt::format(\"File \\\"{}\\\" from \\\"{}\\\" did not match any of the known hashes.\", filename, sourceVfs.getName());\n            return false;\n        }\n    }\n\n    std::filesystem::path targetPath = targetDirectory / std::filesystem::path(std::u8string_view((const char8_t *)(pair.first)));\n    std::filesystem::path parentPath = targetPath.parent_path();\n    if (!std::filesystem::exists(parentPath))\n    {\n        std::error_code ec;\n        std::filesystem::create_directories(parentPath, ec);\n    }\n    \n    while (!parentPath.empty()) {\n        journal.createdDirectories.insert(parentPath);\n\n        if (parentPath != targetDirectory) {\n            parentPath = parentPath.parent_path();\n        }\n        else {\n            parentPath = std::filesystem::path();\n        }\n    }\n\n    std::ofstream outStream(targetPath, std::ios::binary);\n    if (!outStream.is_open())\n    {\n        journal.lastResult = Journal::Result::FileCreationFailed;\n        journal.lastErrorMessage = fmt::format(\"Failed to create file at \\\"{}\\\".\", fromPath(targetPath));\n        return false;\n    }\n\n    journal.createdFiles.push_back(targetPath);\n\n    outStream.write((const char *)(fileData.data()), fileData.size());\n    if (outStream.bad())\n    {\n        journal.lastResult = Journal::Result::FileWriteFailed;\n        journal.lastErrorMessage = fmt::format(\"Failed to create file at \\\"{}\\\".\", fromPath(targetPath));\n        return false;\n    }\n\n    journal.progressCounter += fileData.size();\n    \n    if (!progressCallback())\n    {\n        journal.lastResult = Journal::Result::Cancelled;\n        journal.lastErrorMessage = \"Installation was cancelled.\";\n        return false;\n    }\n\n    return true;\n}\n\nstatic DLC detectDLC(const std::filesystem::path &sourcePath, VirtualFileSystem &sourceVfs, Journal &journal)\n{\n    std::string name;\n    std::ifstream dlcFile(sourcePath);\n\n    dlcFile.seekg(0x412, std::ios::beg);\n\n    char ch;\n    while (dlcFile.get(ch) && ch != '\\0') {\n        // If we're reading an invalid file, don't keep reading\n        // past the maximum length of a valid DLC file name\n        if (name.length() > 41) {\n            break;\n        }\n\n        name += ch;\n        // DLC file names have one character proceeded by null,\n        // so we skip every other byte to read a continuous string\n        dlcFile.seekg(1, std::ios::cur);\n    }\n\n    dlcFile.close();\n\n    if (name == \"Additional Episode \\\"Sonic Boss Attack\\\"\") {\n        return DLC::EpisodeSonic;\n    } else if (name == \"Additional Episode \\\"Shadow Boss Attack\\\"\") {\n        return DLC::EpisodeShadow;\n    } else if (name == \"Additional Episode \\\"Silver Boss Attack\\\"\") {\n        return DLC::EpisodeSilver;\n    } else if (name == \"Additional Episode \\\"Team Attack Amigo\\\"\") {\n        return DLC::EpisodeAmigo;\n    } else if (name == \"Additional Mission Pack \\\"Sonic/Very Hard\") {\n        return DLC::MissionSonic;\n    } else if (name == \"Additional Mission Pack \\\"Shadow/Very Har\") {\n        return DLC::MissionShadow;\n    } else if (name == \"Additional Mission Pack \\\"Silver/Very Har\") {\n        return DLC::MissionSilver;\n    }\n\n    journal.lastResult = Journal::Result::UnknownDLCType;\n    journal.lastErrorMessage = fmt::format(\"DLC type for \\\"{}\\\" is unknown.\", name);\n    return DLC::Unknown;\n}\n\nstatic bool fillDLCSource(DLC dlc, Installer::DLCSource &dlcSource) \n{\n    switch (dlc)\n    {\n    case DLC::EpisodeSonic:\n        dlcSource.filePairs = { EpisodeSonicFiles, EpisodeSonicFilesSize };\n        dlcSource.fileHashes = EpisodeSonicHashes;\n        dlcSource.targetSubDirectory = EpisodeSonicDirectory;\n        return true;\n    case DLC::EpisodeShadow:\n        dlcSource.filePairs = { EpisodeShadowFiles, EpisodeShadowFilesSize };\n        dlcSource.fileHashes = EpisodeShadowHashes;\n        dlcSource.targetSubDirectory = EpisodeShadowDirectory;\n        return true;\n    case DLC::EpisodeSilver:\n        dlcSource.filePairs = { EpisodeSilverFiles, EpisodeSilverFilesSize };\n        dlcSource.fileHashes = EpisodeSilverHashes;\n        dlcSource.targetSubDirectory = EpisodeSilverDirectory;\n        return true;\n    case DLC::EpisodeAmigo:\n        dlcSource.filePairs = { EpisodeAmigoFiles, EpisodeAmigoFilesSize };\n        dlcSource.fileHashes = EpisodeAmigoHashes;\n        dlcSource.targetSubDirectory = EpisodeAmigoDirectory;\n        return true;\n    case DLC::MissionSonic:\n        dlcSource.filePairs = { MissionSonicFiles, MissionSonicFilesSize };\n        dlcSource.fileHashes = MissionSonicHashes;\n        dlcSource.targetSubDirectory = MissionSonicDirectory;\n        return true;\n    case DLC::MissionShadow:\n        dlcSource.filePairs = { MissionShadowFiles, MissionShadowFilesSize };\n        dlcSource.fileHashes = MissionShadowHashes;\n        dlcSource.targetSubDirectory = MissionShadowDirectory;\n        return true;\n    case DLC::MissionSilver:\n        dlcSource.filePairs = { MissionSilverFiles, MissionSilverFilesSize };\n        dlcSource.fileHashes = MissionSilverHashes;\n        dlcSource.targetSubDirectory = MissionSilverDirectory;\n        return true;\n    default:\n        return false;\n    }\n}\n\nbool Installer::checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath)\n{\n    modulePath = baseDirectory / GameDirectory / GameExecutableFile;\n\n    if (!std::filesystem::exists(modulePath))\n        return false;\n\n    return true;\n}\n\nbool Installer::checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc)\n{\n    switch (dlc)\n    {\n    case DLC::EpisodeSonic:\n        return std::filesystem::exists(baseDirectory / EpisodeSonicDirectory / DLCValidationFile);\n    case DLC::EpisodeShadow:\n        return std::filesystem::exists(baseDirectory / EpisodeShadowDirectory / DLCValidationFile);\n    case DLC::EpisodeSilver:\n        return std::filesystem::exists(baseDirectory / EpisodeSilverDirectory / DLCValidationFile);\n    case DLC::EpisodeAmigo:\n        return std::filesystem::exists(baseDirectory / EpisodeAmigoDirectory / DLCValidationFile);\n    case DLC::MissionSonic:\n        return std::filesystem::exists(baseDirectory / MissionSonicDirectory / DLCValidationFile);\n    case DLC::MissionShadow:\n        return std::filesystem::exists(baseDirectory / MissionShadowDirectory / DLCValidationFile);\n    case DLC::MissionSilver:\n        return std::filesystem::exists(baseDirectory / MissionSilverDirectory / DLCValidationFile);\n    default:\n        return false;\n    }\n}\n\nbool Installer::checkAllDLC(const std::filesystem::path& baseDirectory)\n{\n    bool result = true;\n\n    for (int i = 1; i < (int)DLC::Count; i++)\n    {\n        if (!checkDLCInstall(baseDirectory, (DLC)i))\n            result = false;\n    }\n\n    return result;\n}\n\nbool Installer::checkInstallIntegrity(const std::filesystem::path &baseDirectory, Journal &journal, const std::function<bool()> &progressCallback)\n{\n    // Run the file checks twice: once to fill out the progress counter and the file sizes, and another pass to do the hash integrity checks.\n    for (uint32_t checkPass = 0; checkPass < 2; checkPass++)\n    {\n        bool checkSizeOnly = (checkPass == 0);\n        if (!checkFiles({ GameFiles, GameFilesSize }, GameHashes, baseDirectory / GameDirectory, journal, progressCallback, checkSizeOnly))\n        {\n            return false;\n        }\n\n        for (int i = 1; i < (int)DLC::Count; i++)\n        {\n            if (checkDLCInstall(baseDirectory, (DLC)i))\n            {\n                Installer::DLCSource dlcSource;\n                fillDLCSource((DLC)i, dlcSource);\n\n                if (!checkFiles(dlcSource.filePairs, dlcSource.fileHashes, baseDirectory / dlcSource.targetSubDirectory, journal, progressCallback, checkSizeOnly))\n                {\n                    return false;\n                }\n            }\n        }\n    }\n\n    return true;\n}\n\nbool Installer::computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize)\n{\n    for (FilePair pair : filePairs)\n    {\n        const std::string filename(pair.first);\n        if (!sourceVfs.exists(filename))\n        {\n            journal.lastResult = Journal::Result::FileMissing;\n            journal.lastErrorMessage = fmt::format(\"File \\\"{}\\\" does not exist in \\\"{}\\\".\", filename, sourceVfs.getName());\n            return false;\n        }\n\n        totalSize += sourceVfs.getSize(filename);\n    }\n\n    return true;\n}\n\nbool Installer::checkFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<bool()> &progressCallback, bool checkSizeOnly)\n{\n    FilePair validationPair = {};\n    uint32_t validationHashIndex = 0;\n    uint32_t hashIndex = 0;\n    uint32_t hashCount = 0;\n    std::vector<uint8_t> fileData;\n    for (FilePair pair : filePairs)\n    {\n        hashIndex = hashCount;\n        hashCount += pair.second;\n\n        if (!checkFile(pair, &fileHashes[hashIndex], targetDirectory, fileData, journal, progressCallback, checkSizeOnly))\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n\nbool Installer::copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback)\n{\n    std::error_code ec;\n    if (!std::filesystem::exists(targetDirectory) && !std::filesystem::create_directories(targetDirectory, ec))\n    {\n        journal.lastResult = Journal::Result::DirectoryCreationFailed;\n        journal.lastErrorMessage = \"Unable to create directory at \\\"\" + fromPath(targetDirectory) + \"\\\".\";\n        return false;\n    }\n\n    uint32_t hashIndex = 0;\n    uint32_t hashCount = 0;\n    std::vector<uint8_t> fileData;\n    for (FilePair pair : filePairs)\n    {\n        hashIndex = hashCount;\n        hashCount += pair.second;\n\n        if (!copyFile(pair, &fileHashes[hashIndex], sourceVfs, targetDirectory, skipHashChecks, fileData, journal, progressCallback))\n        {\n            return false;\n        }\n    }\n\n    return true;\n}\n\nbool Installer::parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal)\n{\n    targetVfs = createFileSystemFromPath(sourcePath);\n    if (targetVfs != nullptr)\n    {\n        return true;\n    }\n    else\n    {\n        journal.lastResult = Journal::Result::VirtualFileSystemFailed;\n        journal.lastErrorMessage = \"Unable to open \\\"\" + fromPath(sourcePath) + \"\\\".\";\n        return false;\n    }\n}\n\nbool Installer::parseSources(const Input &input, Journal &journal, Sources &sources)\n{\n    journal = Journal();\n    sources = Sources();\n\n    // Parse the contents of the base game.\n    if (!input.gameSource.empty())\n    {\n        if (!parseContent(input.gameSource, sources.game, journal))\n        {\n            return false;\n        }\n\n        if (!computeTotalSize({ GameFiles, GameFilesSize }, GameHashes, *sources.game, journal, sources.totalSize))\n        {\n            return false;\n        }\n    }\n\n    // Parse the contents of the DLC Packs.\n    for (const auto &path : input.dlcSources)\n    {\n        sources.dlc.emplace_back();\n        DLCSource &dlcSource = sources.dlc.back();\n        if (!parseContent(path, dlcSource.sourceVfs, journal))\n        {\n            return false;\n        }\n\n        DLC dlc = detectDLC(path, *dlcSource.sourceVfs, journal);\n        if (!fillDLCSource(dlc, dlcSource))\n        {\n            return false;\n        }\n\n        if (!computeTotalSize(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, journal, sources.totalSize))\n        {\n            return false;\n        }\n    }\n\n    // Add the total size in bytes as the journal progress.\n    journal.progressTotal += sources.totalSize;\n\n    return true;\n}\n\nbool Installer::install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, std::chrono::seconds endWaitTime, const std::function<bool()> &progressCallback)\n{\n    // Install files in reverse order of importance. In case of a process crash or power outage, this will increase the likelihood of the installation\n    // missing critical files required for the game to run. These files are used as the way to detect if the game is installed.\n\n    // Install the DLC.\n    if (!sources.dlc.empty())\n    {\n        journal.createdDirectories.insert(targetDirectory / DLCDirectory);\n    }\n\n    for (const DLCSource &dlcSource : sources.dlc)\n    {\n        if (!copyFiles(dlcSource.filePairs, dlcSource.fileHashes, *dlcSource.sourceVfs, targetDirectory / dlcSource.targetSubDirectory, DLCValidationFile, skipHashChecks, journal, progressCallback))\n        {\n            return false;\n        }\n    }\n\n    // If no game was specified, we're finished. This means the user was only installing the DLC.\n    if ((sources.game == nullptr))\n    {\n        return true;\n    }\n\n    // Install the base game.\n    if (!copyFiles({ GameFiles, GameFilesSize }, GameHashes, *sources.game, targetDirectory / GameDirectory, GameExecutableFile, skipHashChecks, journal, progressCallback))\n    {\n        return false;\n    }\n    \n    for (uint32_t i = 0; i < 2; i++)\n    {\n        if (!progressCallback())\n        {\n            journal.lastResult = Journal::Result::Cancelled;\n            journal.lastErrorMessage = \"Installation was cancelled.\";\n            return false;\n        }\n\n        if (i == 0)\n        {\n            // Wait the specified amount of time to allow the consumer of the callbacks to animate, halt or cancel the installation for a while after it's finished.\n            std::this_thread::sleep_for(endWaitTime);\n        }\n    }\n\n    return true;\n}\n\nvoid Installer::rollback(Journal &journal)\n{\n    std::error_code ec;\n    for (const auto &path : journal.createdFiles)\n    {\n        std::filesystem::remove(path, ec);\n    }\n\n    for (auto it = journal.createdDirectories.rbegin(); it != journal.createdDirectories.rend(); it++)\n    {\n        std::filesystem::remove(*it, ec);\n    }\n}\n\nbool Installer::parseGame(const std::filesystem::path &sourcePath)\n{\n    std::unique_ptr<VirtualFileSystem> sourceVfs = createFileSystemFromPath(sourcePath);\n    if (sourceVfs == nullptr)\n    {\n        return false;\n    }\n\n    return sourceVfs->exists(GameExecutableFile);\n}\n\nDLC Installer::parseDLC(const std::filesystem::path &sourcePath)\n{\n    Journal journal;\n    std::unique_ptr<VirtualFileSystem> sourceVfs = createFileSystemFromPath(sourcePath);\n    if (sourceVfs == nullptr)\n    {\n        return DLC::Unknown;\n    }\n\n    return detectDLC(sourcePath, *sourceVfs, journal);\n}\n"
  },
  {
    "path": "MarathonRecomp/install/installer.h",
    "content": "#pragma once\n\n#include <span>\n#include <set>\n\n#include \"virtual_file_system.h\"\n\nenum class DLC {\n    Unknown,\n    EpisodeSonic,\n    EpisodeShadow,\n    EpisodeSilver,\n    EpisodeAmigo,\n    MissionSonic,\n    MissionShadow,\n    MissionSilver,\n    Count = MissionSilver\n};\n\nstruct Journal\n{\n    enum class Result\n    {\n        Success,\n        Cancelled,\n        VirtualFileSystemFailed,\n        DirectoryCreationFailed,\n        FileMissing,\n        FileReadFailed,\n        FileHashFailed,\n        FileCreationFailed,\n        FileWriteFailed,\n        ValidationFileMissing,\n        DLCParsingFailed,\n        UnknownDLCType\n    };\n\n    uint64_t progressCounter = 0;\n    uint64_t progressTotal = 0;\n    std::list<std::filesystem::path> createdFiles;\n    std::set<std::filesystem::path> createdDirectories;\n    Result lastResult = Result::Success;\n    std::string lastErrorMessage;\n};\n\nusing FilePair = std::pair<const char *, uint32_t>;\n\nstruct Installer\n{\n    struct Input\n    {\n        std::filesystem::path gameSource;\n        std::list<std::filesystem::path> dlcSources;\n    };\n\n    struct DLCSource {\n        std::unique_ptr<VirtualFileSystem> sourceVfs;\n        std::span<const FilePair> filePairs;\n        const uint64_t *fileHashes = nullptr;\n        std::string targetSubDirectory;\n    };\n\n    struct Sources \n    {\n        std::unique_ptr<VirtualFileSystem> game;\n        std::vector<DLCSource> dlc;\n        uint64_t totalSize = 0;\n    };\n\n    static bool checkGameInstall(const std::filesystem::path &baseDirectory, std::filesystem::path &modulePath);\n    static bool checkDLCInstall(const std::filesystem::path &baseDirectory, DLC dlc);\n    static bool checkAllDLC(const std::filesystem::path &baseDirectory);\n    static bool checkInstallIntegrity(const std::filesystem::path &baseDirectory, Journal &journal, const std::function<bool()> &progressCallback);\n    static bool computeTotalSize(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, Journal &journal, uint64_t &totalSize);\n    static bool checkFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, const std::filesystem::path &targetDirectory, Journal &journal, const std::function<bool()> &progressCallback, bool checkSizeOnly);\n    static bool copyFiles(std::span<const FilePair> filePairs, const uint64_t *fileHashes, VirtualFileSystem &sourceVfs, const std::filesystem::path &targetDirectory, const std::string &validationFile, bool skipHashChecks, Journal &journal, const std::function<bool()> &progressCallback);\n    static bool parseContent(const std::filesystem::path &sourcePath, std::unique_ptr<VirtualFileSystem> &targetVfs, Journal &journal);\n    static bool parseSources(const Input &input, Journal &journal, Sources &sources);\n    static bool install(const Sources &sources, const std::filesystem::path &targetDirectory, bool skipHashChecks, Journal &journal, std::chrono::seconds endWaitTime, const std::function<bool()> &progressCallback);\n    static void rollback(Journal &journal);\n\n    // Convenience method for checking if the specified file contains the game. This should be used when the user selects the file.\n    static bool parseGame(const std::filesystem::path &sourcePath);\n\n    // Convenience method for the installer to check which DLC the file that was specified corresponds to. This should be used when the user selects the file.\n    static DLC parseDLC(const std::filesystem::path &sourcePath);\n};\n"
  },
  {
    "path": "MarathonRecomp/install/iso_file_system.cpp",
    "content": "// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/disc_image_device.cc\n\n/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2023 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n#include \"iso_file_system.h\"\n\n#include <stack>\n\nISOFileSystem::ISOFileSystem(const std::filesystem::path &isoPath)\n{\n    mappedFile.open(isoPath);\n    if (!mappedFile.isOpen())\n    {\n        return;\n    }\n\n    name = (const char *)(isoPath.filename().u8string().data());\n\n    // Find root sector.\n    const uint8_t *mappedFileData = mappedFile.data();\n    uint32_t gameOffset = 0;\n    const size_t XeSectorSize = 2048;\n    static const size_t PossibleOffsets[] = { 0x00000000, 0x0000FB20, 0x00020600, 0x02080000, 0x0FD90000, };\n    bool magicFound = false;\n    const char RefMagic[] = \"MICROSOFT*XBOX*MEDIA\";\n    for (size_t i = 0; i < std::size(PossibleOffsets); i++)\n    {\n        size_t fileOffset = PossibleOffsets[i] + (32 * XeSectorSize);\n        if ((fileOffset + strlen(RefMagic)) > mappedFile.size())\n        {\n            continue;\n        }\n\n        if (std::memcmp(&mappedFileData[fileOffset], RefMagic, strlen(RefMagic)) == 0)\n        {\n            gameOffset = PossibleOffsets[i];\n            magicFound = true;\n        }\n    }\n\n    size_t rootInfoOffset = gameOffset + (32 * XeSectorSize) + 20;\n    if (!magicFound || (rootInfoOffset + 8) > mappedFile.size())\n    {\n        mappedFile.close();\n        return;\n    }\n\n    // Parse root information.\n    uint32_t rootSector = *(uint32_t *)(&mappedFileData[rootInfoOffset + 0]);\n    uint32_t rootSize = *(uint32_t *)(&mappedFileData[rootInfoOffset + 4]);\n    size_t rootOffset = gameOffset + (rootSector * XeSectorSize);\n    const uint32_t MinRootSize = 13;\n    const uint32_t MaxRootSize = 32 * 1024 * 1024;\n    if ((rootSize < MinRootSize) || (rootSize > MaxRootSize))\n    {\n        mappedFile.close();\n        return;\n    }\n\n    struct IterationStep\n    {\n        std::string fileNameBase;\n        size_t nodeOffset = 0;\n        size_t entryOffset = 0;\n\n        IterationStep() = default;\n        IterationStep(std::string fileNameBase, size_t nodeOffset, size_t entryOffset) : fileNameBase(fileNameBase), nodeOffset(nodeOffset), entryOffset(entryOffset) { }\n    };\n\n    std::stack<IterationStep> iterationStack;\n    iterationStack.emplace(\"\", rootOffset, 0);\n\n    IterationStep step;\n    uint16_t nodeL, nodeR;\n    uint32_t sector, length;\n    uint8_t attributes, nameLength;\n    char fileName[256];\n    const uint8_t FileAttributeDirectory = 0x10;\n    while (!iterationStack.empty())\n    {\n        step = iterationStack.top();\n        iterationStack.pop();\n\n        size_t infoOffset = step.nodeOffset + step.entryOffset;\n        if ((infoOffset + 14) > mappedFile.size())\n        {\n            mappedFile.close();\n            return;\n        }\n\n        nodeL = *(uint16_t *)(&mappedFileData[infoOffset + 0]);\n        nodeR = *(uint16_t *)(&mappedFileData[infoOffset + 2]);\n        sector = *(uint32_t *)(&mappedFileData[infoOffset + 4]);\n        length = *(uint32_t *)(&mappedFileData[infoOffset + 8]);\n        attributes = *(uint8_t *)(&mappedFileData[infoOffset + 12]);\n        nameLength = *(uint8_t *)(&mappedFileData[infoOffset + 13]);\n\n        size_t nameOffset = infoOffset + 14;\n        if ((nameOffset + nameLength) > mappedFile.size())\n        {\n            mappedFile.close();\n            return;\n        }\n\n        memcpy(fileName, &mappedFileData[nameOffset], nameLength);\n        fileName[nameLength] = '\\0';\n\n        if (nodeL)\n        {\n            iterationStack.emplace(step.fileNameBase, step.nodeOffset, nodeL * 4);\n        }\n\n        if (nodeR)\n        {\n            iterationStack.emplace(step.fileNameBase, step.nodeOffset, nodeR * 4);\n        }\n\n        std::string fileNameUTF8 = step.fileNameBase + fileName;\n        if (attributes & FileAttributeDirectory)\n        {\n            if (length > 0)\n            {\n                iterationStack.emplace(fileNameUTF8 + \"/\", gameOffset + sector * XeSectorSize, 0);\n            }\n        }\n        else\n        {\n            fileMap[fileNameUTF8] = { gameOffset + sector * XeSectorSize, length};\n        }\n    }\n}\n\nbool ISOFileSystem::load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const\n{\n    auto it = fileMap.find(path);\n    if (it != fileMap.end())\n    {\n        if (fileDataMaxByteCount < std::get<1>(it->second))\n        {\n            return false;\n        }\n\n        const uint8_t *mappedFileData = mappedFile.data();\n        memcpy(fileData, &mappedFileData[std::get<0>(it->second)], std::get<1>(it->second));\n        return true;\n    }\n    else\n    {\n        return false;\n    }\n}\n\nsize_t ISOFileSystem::getSize(const std::string &path) const\n{\n    auto it = fileMap.find(path);\n    if (it != fileMap.end())\n    {\n        return std::get<1>(it->second);\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nbool ISOFileSystem::exists(const std::string &path) const\n{\n    return fileMap.find(path) != fileMap.end();\n}\n\nconst std::string &ISOFileSystem::getName() const\n{\n    return name;\n}\n\nbool ISOFileSystem::empty() const \n{\n    return !mappedFile.isOpen();\n}\n\nstd::unique_ptr<ISOFileSystem> ISOFileSystem::create(const std::filesystem::path &isoPath) {\n    std::unique_ptr<ISOFileSystem> isoFs = std::make_unique<ISOFileSystem>(isoPath);\n    if (!isoFs->empty())\n    {\n        return isoFs;\n    }\n    else\n    {\n        return nullptr;\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/install/iso_file_system.h",
    "content": "// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/disc_image_device.cc\n\n/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2023 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n#pragma once\n\n#include <filesystem>\n#include <map>\n\n#include \"virtual_file_system.h\"\n\n#include <memory_mapped_file.h>\n\nstruct ISOFileSystem : VirtualFileSystem\n{\n    MemoryMappedFile mappedFile;\n    std::map<std::string, std::tuple<size_t, size_t>> fileMap;\n    std::string name;\n\n    ISOFileSystem(const std::filesystem::path &isoPath);\n    bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const override;\n    size_t getSize(const std::string &path) const override;\n    bool exists(const std::string &path) const override;\n    const std::string &getName() const override;\n    bool empty() const;\n\n    static std::unique_ptr<ISOFileSystem> create(const std::filesystem::path &isoPath);\n};\n"
  },
  {
    "path": "MarathonRecomp/install/update_checker.cpp",
    "content": "#include \"update_checker.h\"\n\n#include <curl/curl.h>\n#include <nlohmann/json.hpp>\n\n#include \"version.h\"\n\n#ifdef WIN32\n#include <shellapi.h>\n#endif\n\n#include <os/logger.h>\n\n// UpdateChecker\n\nusing json = nlohmann::json;\n\nstatic const char *CHECK_URL = \"https://api.github.com/repos/sonicnext-dev/MarathonRecomp/releases/latest\";\nstatic const char *VISIT_URL = \"https://github.com/sonicnext-dev/MarathonRecomp/releases/latest\";\nstatic const char *USER_AGENT = \"MarathonRecomp-Agent\";\n\nstatic std::atomic<bool> g_updateCheckerInProgress = false;\nstatic std::atomic<bool> g_updateCheckerFinished = false;\nstatic UpdateChecker::Result g_updateCheckerResult = UpdateChecker::Result::NotStarted;\n\nsize_t updateCheckerWriteCallback(void *contents, size_t size, size_t nmemb, std::string *output)\n{\n    size_t totalSize = size * nmemb;\n    output->append((char *)contents, totalSize);\n    return totalSize;\n}\n\nstatic bool parseVersion(const std::string &versionStr, int &major, int &minor, int &revision)\n{\n    size_t start = 0;\n    if (versionStr[0] == 'v')\n    {\n        start = 1;\n    }\n\n    size_t firstDot = versionStr.find('.', start);\n    size_t secondDot = versionStr.find('.', firstDot + 1);\n\n    if (firstDot == std::string::npos || secondDot == std::string::npos)\n    {\n        return false;\n    }\n\n    try\n    {\n        major = std::stoi(versionStr.substr(start, firstDot - start));\n        minor = std::stoi(versionStr.substr(firstDot + 1, secondDot - firstDot - 1));\n        revision = std::stoi(versionStr.substr(secondDot + 1));\n    }\n    catch (const std::exception &e)\n    {\n        LOGF_ERROR(\"Error while parsing version: {}.\", e.what());\n        return false;\n    }\n\n    return true;\n}\n\nvoid updateCheckerThread()\n{\n    CURL *curl = curl_easy_init();\n    CURLcode res;\n    int major, minor, revision;\n    std::string response;\n    curl_easy_setopt(curl, CURLOPT_URL, CHECK_URL);\n    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, updateCheckerWriteCallback);\n    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);\n    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);\n    curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);\n\n    res = curl_easy_perform(curl);\n    if (res == CURLE_OK)\n    {\n        try\n        {\n            json root = json::parse(response);\n            auto tag_name_element = root.find(\"tag_name\");\n            if (tag_name_element != root.end() && tag_name_element->is_string())\n            {\n                if (parseVersion(*tag_name_element, major, minor, revision))\n                {\n                    if ((g_versionMajor < major) || (g_versionMajor == major  && g_versionMinor < minor) || (g_versionMajor == major && g_versionMinor == minor && g_versionRevision < revision))\n                    {\n                        g_updateCheckerResult = UpdateChecker::Result::UpdateAvailable;\n                    }\n                    else\n                    {\n                        g_updateCheckerResult = UpdateChecker::Result::AlreadyUpToDate;\n                    }\n                }\n                else\n                {\n                    LOG_ERROR(\"Error while parsing response: tag_name does not contain a valid version string.\");\n                    g_updateCheckerResult = UpdateChecker::Result::Failed;\n                }\n            }\n            else\n            {\n                LOG_ERROR(\"Error while parsing response: tag_name not found or not the right type.\");\n                g_updateCheckerResult = UpdateChecker::Result::Failed;\n            }\n        }\n        catch (const json::exception &e)\n        {\n            LOGF_ERROR(\"Error while parsing response: {}\", e.what());\n            g_updateCheckerResult = UpdateChecker::Result::Failed;\n        }\n    }\n    else\n    {\n        LOGF_ERROR(\"Error while performing request: {}\", curl_easy_strerror(res));\n        g_updateCheckerResult = UpdateChecker::Result::Failed;\n    }\n\n    curl_easy_cleanup(curl);\n\n    g_updateCheckerFinished = true;\n    g_updateCheckerInProgress = false;\n}\n\nvoid UpdateChecker::initialize()\n{\n    curl_global_init(CURL_GLOBAL_DEFAULT);\n}\n\nbool UpdateChecker::start()\n{\n    if (g_updateCheckerInProgress)\n    {\n        return false;\n    }\n\n    g_updateCheckerInProgress = true;\n    g_updateCheckerFinished = false;\n    std::thread thread(&updateCheckerThread);\n    thread.detach();\n\n    return true;\n}\n\nUpdateChecker::Result UpdateChecker::check()\n{\n    if (g_updateCheckerFinished)\n    {\n        return g_updateCheckerResult;\n    }\n    else if (g_updateCheckerInProgress)\n    {\n        return UpdateChecker::Result::InProgress;\n    }\n    else\n    {\n        return UpdateChecker::Result::NotStarted;\n    }\n}\n\nvoid UpdateChecker::visitWebsite()\n{\n#if defined(WIN32)\n    ShellExecuteA(0, 0, VISIT_URL, 0, 0, SW_SHOW);\n#elif defined(__linux__)\n    std::string command = \"xdg-open \" + std::string(VISIT_URL) + \" &\";\n    std::system(command.c_str());\n#elif defined(__APPLE__)\n    std::string command = \"open \" + std::string(VISIT_URL) + \" &\";\n    std::system(command.c_str());\n#else\n    static_assert(false, \"Visit website not implemented for this platform.\");\n#endif\n}\n"
  },
  {
    "path": "MarathonRecomp/install/update_checker.h",
    "content": "#pragma once\n\nstruct UpdateChecker\n{\n    enum class Result\n    {\n        NotStarted,\n        InProgress,\n        AlreadyUpToDate,\n        UpdateAvailable,\n        Failed\n    };\n\n    static void initialize();\n    static bool start();\n    static Result check();\n    static void visitWebsite();\n};\n"
  },
  {
    "path": "MarathonRecomp/install/virtual_file_system.h",
    "content": "#pragma once\n\n#include <algorithm>\n#include <memory>\n\nstruct VirtualFileSystem {\n    virtual ~VirtualFileSystem() { };\n    virtual bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const = 0;\n    virtual size_t getSize(const std::string &path) const = 0;\n    virtual bool exists(const std::string &path) const = 0;\n    virtual const std::string &getName() const = 0;\n\n    // Concrete implementation shortcut.\n    bool load(const std::string &path, std::vector<uint8_t> &fileData)\n    {\n        size_t fileDataSize = getSize(path);\n        if (fileDataSize == 0)\n        {\n            return false;\n        }\n\n        fileData.resize(fileDataSize);\n        return load(path, fileData.data(), fileDataSize);\n    }\n};\n"
  },
  {
    "path": "MarathonRecomp/install/xcontent_file_system.cpp",
    "content": "// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/xcontent_container_device.cc\n\n/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2023 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n\n#include \"xcontent_file_system.h\"\n\n#include <bit>\n#include <set>\n#include <stack>\n\nenum class XContentPackageType\n{\n    CON = 0x434F4E20,\n    PIRS = 0x50495253,\n    LIVE = 0x4C495645,\n};\n\nstruct XContentLicense\n{\n    be<uint64_t> licenseId;\n    be<uint32_t> licenseBits;\n    be<uint32_t> licenseFlags;\n};\n\n#pragma pack(push, 1)\nstruct XContentHeader\n{\n    be<uint32_t> magic;\n    uint8_t signature[0x228];\n    XContentLicense licenses[0x10];\n    uint8_t contentId[0x14];\n    be<uint32_t> headerSize;\n};\nstatic_assert(sizeof(XContentHeader) == 0x344);\n\nstruct StfsVolumeDescriptor\n{\n    uint8_t descriptorLength;\n    uint8_t version;\n\n    union\n    {\n        uint8_t asByte;\n        struct\n        {\n            uint8_t readOnlyFormat : 1;\n            uint8_t rootActiveIndex : 1;\n            uint8_t directoryOverallocated : 1;\n            uint8_t directoryIndexBoundsValid : 1;\n        } bits;\n    } flags;\n\n    uint16_t fileTableBlockCount;\n    uint8_t fileTableBlockNumberRaw[3];\n    uint8_t topHashTableHash[0x14];\n    be<uint32_t> totalBlockCount;\n    be<uint32_t> freeBlockCount;\n};\nstatic_assert(sizeof(StfsVolumeDescriptor) == 0x24);\n\nstruct StfsDirectoryEntry {\n    char name[40];\n\n    struct\n    {\n        uint8_t nameLength : 6;\n        uint8_t contiguous : 1;\n        uint8_t directory : 1;\n    } flags;\n\n    uint8_t validDataBlocksRaw[3];\n    uint8_t allocatedDataBlocksRaw[3];\n    uint8_t startBlockNumberRaw[3];\n    be<uint16_t> directoryIndex;\n    be<uint32_t> length;\n    be<uint16_t> createDate;\n    be<uint16_t> createTime;\n    be<uint16_t> modifiedDate;\n    be<uint16_t> modifiedTime;\n};\nstatic_assert(sizeof(StfsDirectoryEntry) == 0x40);\n\nstruct StfsDirectoryBlock {\n    StfsDirectoryEntry entries[0x40];\n};\nstatic_assert(sizeof(StfsDirectoryBlock) == 0x1000);\n\nstruct StfsHashEntry {\n    uint8_t sha1[0x14];\n    be<uint32_t> infoRaw;\n};\nstatic_assert(sizeof(StfsHashEntry) == 0x18);\n\nstruct StfsHashTable {\n    StfsHashEntry entries[170];\n    be<uint32_t> numBlocks;\n    uint8_t padding[12];\n};\nstatic_assert(sizeof(StfsHashTable) == 0x1000);\n\nstruct SvodDeviceDescriptor {\n    uint8_t descriptorLength;\n    uint8_t blockCacheElementCount;\n    uint8_t workerThreadProcessor;\n    uint8_t workerThreadPriority;\n    uint8_t firstFragmentHashEntry[0x14];\n    union {\n        uint8_t asByte;\n        struct {\n            uint8_t mustBeZeroForFutureUsage : 6;\n            uint8_t enhancedGdfLayout : 1;\n            uint8_t zeroForDownlevelClients : 1;\n        } bits;\n    } features;\n    uint8_t numDataBlocksRaw[3];\n    uint8_t startDataBlockRaw[3];\n    uint8_t reserved[5];\n};\nstatic_assert(sizeof(SvodDeviceDescriptor) == 0x24);\n\nstruct SvodDirectoryEntry {\n    uint16_t nodeL;\n    uint16_t nodeR;\n    uint32_t dataBlock;\n    uint32_t length;\n    uint8_t attributes;\n    uint8_t nameLength;\n};\nstatic_assert(sizeof(SvodDirectoryEntry) == 0xE);\n\nstruct XContentMetadata\n{\n    be<uint32_t> contentType;\n    be<uint32_t> metadataVersion;\n    be<uint64_t> contentSize;\n    uint8_t executionInfo[24];\n    uint8_t consoleId[5];\n    be<uint64_t> profileId;\n\n    union {\n        StfsVolumeDescriptor stfsVolumeDescriptor;\n        SvodDeviceDescriptor svodDeviceDescriptor;\n    };\n\n    be<uint32_t> dataFileCount;\n    be<uint64_t> dataFileSize;\n    be<uint32_t> volumeType;\n    be<uint64_t> onlineCreator;\n    be<uint32_t> category;\n};\nstatic_assert(sizeof(XContentMetadata) == 0x75);\n\n#pragma pack(pop)\n\nstruct XContentContainerHeader\n{\n    XContentHeader contentHeader;\n    XContentMetadata contentMetadata;\n};\n\nconst uint32_t StfsBlockSize = 0x1000;\nconst uint32_t StfsBlocksHashLevelAmount = 3;\nconst uint32_t StfsBlocksPerHashLevel[StfsBlocksHashLevelAmount] = { 170, 28900, 4913000 };\nconst uint32_t StfsEndOfChain = 0xFFFFFF;\nconst uint32_t StfsEntriesPerDirectoryBlock = StfsBlockSize / sizeof(StfsDirectoryEntry);\n\nuint32_t parseUint24(const uint8_t *bytes) {\n    return bytes[0] | (bytes[1] << 8U) | (bytes[2] << 16U);\n}\n\nsize_t blockIndexToOffset(uint64_t baseOffset, uint64_t blockIndex)\n{\n    uint64_t block = blockIndex;\n    for (uint32_t i = 0; i < StfsBlocksHashLevelAmount; i++)\n    {\n        uint32_t levelBase = StfsBlocksPerHashLevel[i];\n        block += ((blockIndex + levelBase) / levelBase);\n        if (blockIndex < levelBase)\n        {\n            break;\n        }\n    }\n\n    return baseOffset + (block << 12);\n}\n\nuint32_t blockIndexToHashBlockNumber(uint32_t blockIndex) {\n    if (blockIndex < StfsBlocksPerHashLevel[0])\n    {\n        return 0;\n    }\n\n    uint32_t block = (blockIndex / StfsBlocksPerHashLevel[0]) * (StfsBlocksPerHashLevel[0] + 1);\n    block += ((blockIndex / StfsBlocksPerHashLevel[1]) + 1);\n    if (blockIndex < StfsBlocksPerHashLevel[1])\n    {\n        return block;\n    }\n\n    return block + 1;\n}\n\nsize_t blockIndexToHashBlockOffset(uint64_t baseOffset, uint32_t blockIndex)\n{\n    size_t blockNumber = blockIndexToHashBlockNumber(blockIndex);\n    return baseOffset + (blockNumber << 12);\n}\n\nconst StfsHashEntry *hashEntryFromBlockIndex(const uint8_t *fileData, uint64_t baseOffset, uint64_t blockIndex)\n{\n    size_t hashOffset = blockIndexToHashBlockOffset(baseOffset, blockIndex);\n    const StfsHashTable *hashTable = (const StfsHashTable *)(&fileData[hashOffset]);\n    return &hashTable->entries[blockIndex % StfsBlocksPerHashLevel[0]];\n}\n\nvoid blockToOffsetAndFile(SvodLayoutType svodLayoutType, size_t svodStartDataBlock, size_t svodBaseOffset, size_t block, size_t &outOffset, size_t &outFileIndex)\n{\n    const size_t BlockSize = 0x800;\n    const size_t HashBlockSize = 0x1000;\n    const size_t BlocksPerL0Hash = 0x198;\n    const size_t HashesPerL1Hash = 0xA1C4;\n    const size_t BlocksPerFile = 0x14388;\n    const size_t MaxFileSize = 0xA290000;\n    size_t trueBlock = block - (svodStartDataBlock * 2);\n    if (svodLayoutType == SvodLayoutType::EnhancedGDF)\n    {\n        trueBlock += 0x2;\n    }\n\n    size_t fileBlock = trueBlock % BlocksPerFile;\n    outFileIndex = trueBlock / BlocksPerFile;\n\n    size_t offset = 0;\n    size_t level0TableCount = (fileBlock / BlocksPerL0Hash) + 1;\n    offset += level0TableCount * HashBlockSize;\n\n    size_t level1TableCount = (level0TableCount / HashesPerL1Hash) + 1;\n    offset += level1TableCount * HashBlockSize;\n\n    if (svodLayoutType == SvodLayoutType::SingleFile)\n    {\n        offset += svodBaseOffset;\n    }\n\n    outOffset = (fileBlock * BlockSize) + offset;\n    if (outOffset >= MaxFileSize)\n    {\n        outOffset = (outOffset % MaxFileSize) + 0x2000;\n        outFileIndex++;\n    }\n}\n\nXContentFileSystem::XContentFileSystem(const std::filesystem::path &contentPath)\n{\n    mappedFiles.emplace_back();\n\n    MemoryMappedFile &rootMappedFile = mappedFiles.back();\n    rootMappedFile.open(contentPath);\n    if (!rootMappedFile.isOpen())\n    {\n        return;\n    }\n\n    name = (const char *)(contentPath.filename().u8string().data());\n\n    const uint8_t *rootMappedFileData = rootMappedFile.data();\n    if (sizeof(XContentContainerHeader) > rootMappedFile.size())\n    {\n        mappedFiles.clear();\n        return;\n    }\n\n    XContentContainerHeader contentContainerHeader = *(const XContentContainerHeader *)(rootMappedFileData);\n    XContentPackageType packageType = XContentPackageType(contentContainerHeader.contentHeader.magic.get());\n    if (packageType != XContentPackageType::CON && packageType != XContentPackageType::LIVE && packageType != XContentPackageType::PIRS)\n    {\n        mappedFiles.clear();\n        return;\n    }\n\n    const XContentMetadata &metadata = contentContainerHeader.contentMetadata;\n    volumeType = XContentVolumeType(metadata.volumeType.get());\n    if (volumeType == XContentVolumeType::STFS)\n    {\n        const StfsVolumeDescriptor &descriptor = metadata.stfsVolumeDescriptor;\n        if (descriptor.descriptorLength != sizeof(StfsVolumeDescriptor) || !descriptor.flags.bits.readOnlyFormat)\n        {\n            mappedFiles.clear();\n            return;\n        }\n\n        baseOffset = ((contentContainerHeader.contentHeader.headerSize + StfsBlockSize - 1) / StfsBlockSize) * StfsBlockSize;\n\n        uint32_t entryCount = 0;\n        uint32_t tableBlockIndex = parseUint24(descriptor.fileTableBlockNumberRaw);\n        uint32_t tableBlockCount = descriptor.fileTableBlockCount;\n        std::map<uint32_t, std::string> directoryNames;\n        for (uint32_t i = 0; i < tableBlockCount; i++)\n        {\n            size_t offset = blockIndexToOffset(baseOffset, tableBlockIndex);\n            if (offset + sizeof(StfsDirectoryBlock) > rootMappedFile.size())\n            {\n                mappedFiles.clear();\n                return;\n            }\n\n            StfsDirectoryBlock *directoryBlock = (StfsDirectoryBlock *)(&rootMappedFileData[offset]);\n            for (uint32_t j = 0; j < StfsEntriesPerDirectoryBlock; j++)\n            {\n                const StfsDirectoryEntry &directoryEntry = directoryBlock->entries[j];\n                if (directoryEntry.name[0] == '\\0')\n                {\n                    break;\n                }\n\n                std::string fileNameBase = directoryNames[directoryEntry.directoryIndex];\n                std::string fileName(directoryEntry.name, directoryEntry.flags.nameLength & 0x3F);\n                if (directoryEntry.flags.directory)\n                {\n                    directoryNames[entryCount++] = fileNameBase + fileName + \"/\";\n                    continue;\n                }\n\n                uint32_t fileBlockIndex = parseUint24(directoryEntry.startBlockNumberRaw);\n                uint32_t fileBlockCount = parseUint24(directoryEntry.allocatedDataBlocksRaw);\n                fileMap[fileNameBase + fileName] = { directoryEntry.length, fileBlockIndex, fileBlockCount };\n                entryCount++;\n            }\n\n            const StfsHashEntry *hashEntry = hashEntryFromBlockIndex(rootMappedFileData, baseOffset, tableBlockIndex);\n            tableBlockIndex = hashEntry->infoRaw & 0xFFFFFF;\n            if (tableBlockIndex == StfsEndOfChain)\n            {\n                break;\n            }\n        }\n    }\n    else if (volumeType == XContentVolumeType::SVOD)\n    {\n        mappedFiles.clear();\n\n        // Close the root file and open all the files inside the directory with the same name instead.\n        std::filesystem::path dataDirectory(contentPath.u8string() + u8\".data\");\n        if (!std::filesystem::is_directory(dataDirectory))\n        {\n            return;\n        }\n\n        // Find all data files inside the directory.\n        std::set<std::filesystem::path> orderedPaths;\n        for (auto &entry : std::filesystem::directory_iterator(dataDirectory))\n        {\n            if (!entry.is_regular_file())\n            {\n                continue;\n            }\n\n            orderedPaths.emplace(entry.path());\n        }\n\n        // Memory map all the files that were found.\n        for (auto &path : orderedPaths)\n        {\n            mappedFiles.emplace_back();\n            if (!mappedFiles.back().open(path))\n            {\n                mappedFiles.clear();\n                return;\n            }\n        }\n\n        if (mappedFiles.empty())\n        {\n            return;\n        }\n\n        // Determine the layout of the SVOD from the first file.\n        MemoryMappedFile &firstMappedFile = mappedFiles.front();\n        const uint8_t *firstMappedFileData = firstMappedFile.data();\n        const char *RefMagic = \"MICROSOFT*XBOX*MEDIA\";\n        size_t RefXSFMagicOffset = 0x12000;\n        size_t SingleFileMagicOffset = 0xD000;\n        if (metadata.svodDeviceDescriptor.features.bits.enhancedGdfLayout)\n        {\n            size_t EGDFMagicOffset = 0x2000;\n            if (EGDFMagicOffset >= firstMappedFile.size() || std::memcmp(&firstMappedFileData[EGDFMagicOffset], RefMagic, strlen(RefMagic)) != 0)\n            {\n                mappedFiles.clear();\n                return;\n            }\n\n            svodBaseOffset = 0;\n            svodMagicOffset = EGDFMagicOffset;\n            svodLayoutType = SvodLayoutType::EnhancedGDF;\n        }\n        else if (RefXSFMagicOffset < firstMappedFile.size() && std::memcmp(&firstMappedFileData[RefXSFMagicOffset], RefMagic, strlen(RefMagic)) == 0)\n        {\n            const char *XSFMagic = \"XSF\";\n            size_t XSFMagicOffset = 0x2000;\n            svodBaseOffset = 0x10000;\n            svodMagicOffset = 0x12000;\n\n            if (std::memcmp(&firstMappedFileData[XSFMagicOffset], XSFMagic, strlen(XSFMagic)) == 0)\n            {\n                svodLayoutType = SvodLayoutType::XSF;\n            }\n            else\n            {\n                svodLayoutType = SvodLayoutType::Unknown;\n            }\n        }\n        else if (SingleFileMagicOffset < firstMappedFile.size() && std::memcmp(&firstMappedFileData[SingleFileMagicOffset], RefMagic, strlen(RefMagic)) == 0)\n        {\n            svodBaseOffset = 0xB000;\n            svodMagicOffset = 0xD000;\n            svodLayoutType = SvodLayoutType::SingleFile;\n        }\n        else {\n            mappedFiles.clear();\n            return;\n        }\n\n        svodStartDataBlock = parseUint24(metadata.svodDeviceDescriptor.startDataBlockRaw);\n\n        struct IterationStep\n        {\n            std::string fileNameBase;\n            uint32_t blockIndex = 0;\n            uint32_t ordinalIndex = 0;\n\n            IterationStep() = default;\n            IterationStep(std::string fileNameBase, uint32_t blockIndex, uint32_t ordinalIndex) : fileNameBase(fileNameBase), blockIndex(blockIndex), ordinalIndex(ordinalIndex) { }\n        };\n\n        std::stack<IterationStep> iterationStack;\n        uint32_t rootBlock = *(uint32_t *)(&firstMappedFileData[svodMagicOffset + 0x14]);\n        iterationStack.emplace(\"\", rootBlock, 0);\n\n        IterationStep step;\n        size_t fileOffset, fileIndex;\n        char fileName[256];\n        const uint8_t FileAttributeDirectory = 0x10;\n        while (!iterationStack.empty())\n        {\n            step = iterationStack.top();\n            iterationStack.pop();\n\n            size_t ordinalOffset = step.ordinalIndex * 0x4;\n            size_t blockOffset = ordinalOffset / 0x800;\n            size_t trueOrdinalOffset = ordinalOffset % 0x800;\n            blockToOffsetAndFile(svodLayoutType, svodStartDataBlock, svodBaseOffset, step.blockIndex + blockOffset, fileOffset, fileIndex);\n            fileOffset += trueOrdinalOffset;\n            if (fileIndex >= mappedFiles.size())\n            {\n                mappedFiles.clear();\n                return;\n            }\n\n            const MemoryMappedFile &mappedFile = mappedFiles[fileIndex];\n            if ((fileOffset + sizeof(SvodDirectoryEntry)) > mappedFile.size())\n            {\n                mappedFiles.clear();\n                return;\n            }\n\n            const uint8_t *mappedFileData = mappedFile.data();\n            const SvodDirectoryEntry *directoryEntry = (const SvodDirectoryEntry *)(&mappedFileData[fileOffset]);\n            size_t nameOffset = fileOffset + sizeof(SvodDirectoryEntry);\n            if ((nameOffset + directoryEntry->nameLength) > mappedFile.size())\n            {\n                mappedFiles.clear();\n                return;\n            }\n\n            memcpy(fileName, &mappedFileData[nameOffset], directoryEntry->nameLength);\n            fileName[directoryEntry->nameLength] = '\\0';\n\n            if (directoryEntry->nodeL)\n            {\n                iterationStack.emplace(step.fileNameBase, step.blockIndex, directoryEntry->nodeL);\n            }\n\n            if (directoryEntry->nodeR)\n            {\n                iterationStack.emplace(step.fileNameBase, step.blockIndex, directoryEntry->nodeR);\n            }\n\n            std::string fileNameUTF8 = step.fileNameBase + fileName;\n            if (directoryEntry->attributes & FileAttributeDirectory)\n            {\n                if (directoryEntry->length > 0)\n                {\n                    iterationStack.emplace(fileNameUTF8 + \"/\", directoryEntry->dataBlock, 0);\n                }\n            }\n            else\n            {\n                fileMap[fileNameUTF8] = { directoryEntry->length, directoryEntry->dataBlock, 0 };\n            }\n        }\n    }\n    else\n    {\n        mappedFiles.clear();\n    }\n}\n\nbool XContentFileSystem::load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const\n{\n    auto it = fileMap.find(path);\n    if (it != fileMap.end())\n    {\n        if (fileDataMaxByteCount < it->second.size)\n        {\n            return false;\n        }\n\n        if (volumeType == XContentVolumeType::STFS)\n        {\n            const MemoryMappedFile &rootMappedFile = mappedFiles.back();\n            const uint8_t *rootMappedFileData = rootMappedFile.data();\n            size_t fileDataOffset = 0;\n            size_t remainingSize = it->second.size;\n            uint32_t fileBlockIndex = it->second.blockIndex;\n            for (uint32_t i = 0; i < it->second.blockCount && fileBlockIndex != StfsEndOfChain; i++)\n            {\n                size_t blockSize = std::min(size_t(StfsBlockSize), remainingSize);\n                size_t blockOffset = blockIndexToOffset(baseOffset, fileBlockIndex);\n                if (blockOffset + blockSize > rootMappedFile.size())\n                {\n                    return false;\n                }\n\n                memcpy(&fileData[fileDataOffset], &rootMappedFileData[blockOffset], blockSize);\n\n                const StfsHashEntry *hashEntry = hashEntryFromBlockIndex(rootMappedFileData, baseOffset, fileBlockIndex);\n                fileBlockIndex = hashEntry->infoRaw & 0xFFFFFF;\n                fileDataOffset += blockSize;\n                remainingSize -= blockSize;\n            }\n\n            return remainingSize == 0;\n        }\n        else if (volumeType == XContentVolumeType::SVOD)\n        {\n            size_t fileDataOffset = 0;\n            size_t remainingSize = it->second.size;\n            size_t currentBlock = it->second.blockIndex;\n            while (remainingSize > 0)\n            {\n                size_t blockFileOffset, blockFileIndex;\n                blockToOffsetAndFile(svodLayoutType, svodStartDataBlock, svodBaseOffset, currentBlock, blockFileOffset, blockFileIndex);\n                if (blockFileIndex >= mappedFiles.size())\n                {\n                    return false;\n                }\n\n                const MemoryMappedFile &mappedFile = mappedFiles[blockFileIndex];\n                const uint8_t *mappedFileData = mappedFile.data();\n                size_t blockSize = std::min(size_t(0x800), remainingSize);\n                if (blockFileOffset + blockSize > mappedFile.size())\n                {\n                    return false;\n                }\n\n                memcpy(&fileData[fileDataOffset], &mappedFileData[blockFileOffset], blockSize);\n\n                fileDataOffset += blockSize;\n                remainingSize -= blockSize;\n                currentBlock++;\n            }\n\n            return remainingSize == 0;\n        }\n        else\n        {\n            return false;\n        }\n    }\n    else\n    {\n        return false;\n    }\n}\n\nsize_t XContentFileSystem::getSize(const std::string &path) const\n{\n    auto it = fileMap.find(path);\n    if (it != fileMap.end())\n    {\n        return it->second.size;\n    }\n    else\n    {\n        return 0;\n    }\n}\n\nbool XContentFileSystem::exists(const std::string &path) const\n{\n    return fileMap.find(path) != fileMap.end();\n}\n\nconst std::string &XContentFileSystem::getName() const\n{\n    return name;\n}\n\nbool XContentFileSystem::empty() const\n{\n    return mappedFiles.empty();\n}\n\nstd::unique_ptr<XContentFileSystem> XContentFileSystem::create(const std::filesystem::path &contentPath)\n{\n    std::unique_ptr<XContentFileSystem> xContentFS = std::make_unique<XContentFileSystem>(contentPath);\n    if (!xContentFS->empty())\n    {\n        return xContentFS;\n    }\n    else\n    {\n        return nullptr;\n    }\n}\n\nbool XContentFileSystem::check(const std::filesystem::path &contentPath)\n{\n    std::ifstream contentStream(contentPath, std::ios::binary);\n    if (!contentStream.is_open())\n    {\n        return false;\n    }\n\n    uint32_t packageTypeUint = 0;\n    contentStream.read((char *)(&packageTypeUint), sizeof(uint32_t));\n    packageTypeUint = ByteSwap(packageTypeUint);\n    XContentPackageType packageType = XContentPackageType(packageTypeUint);\n    return packageType == XContentPackageType::CON || packageType == XContentPackageType::LIVE || packageType == XContentPackageType::PIRS;\n}\n"
  },
  {
    "path": "MarathonRecomp/install/xcontent_file_system.h",
    "content": "// Referenced from: https://github.com/xenia-canary/xenia-canary/blob/canary_experimental/src/xenia/vfs/devices/xcontent_container_device.cc\n\n/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2023 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n#pragma once\n\n#include <filesystem>\n#include <map>\n\n#include \"virtual_file_system.h\"\n\n#include <memory_mapped_file.h>\n\nenum class XContentVolumeType\n{\n    STFS = 0,\n    SVOD = 1,\n};\n\nenum class SvodLayoutType \n{\n    Unknown = 0x0,\n    EnhancedGDF = 0x1,\n    XSF = 0x2,\n    SingleFile = 0x4,\n};\n\nstruct XContentFileSystem : VirtualFileSystem\n{\n    struct File\n    {\n        size_t size = 0;\n        uint32_t blockIndex = 0;\n        uint32_t blockCount = 0;\n    };\n\n    XContentVolumeType volumeType = XContentVolumeType::STFS;\n    SvodLayoutType svodLayoutType = SvodLayoutType::Unknown;\n    size_t svodStartDataBlock = 0;\n    size_t svodBaseOffset = 0;\n    size_t svodMagicOffset = 0;\n    std::vector<MemoryMappedFile> mappedFiles;\n    uint64_t baseOffset = 0;\n    std::map<std::string, File> fileMap;\n    std::string name;\n\n    XContentFileSystem(const std::filesystem::path &contentPath);\n    bool load(const std::string &path, uint8_t *fileData, size_t fileDataMaxByteCount) const override;\n    size_t getSize(const std::string &path) const override;\n    bool exists(const std::string &path) const override;\n    const std::string &getName() const override;\n    bool empty() const;\n\n    static std::unique_ptr<XContentFileSystem> create(const std::filesystem::path &contentPath);\n    static bool check(const std::filesystem::path &contentPath);\n};\n"
  },
  {
    "path": "MarathonRecomp/kernel/freelist.h",
    "content": "#pragma once\n\ntemplate<typename T>\nstruct FreeList\n{\n    std::vector<T> items;\n    std::vector<size_t> freed{};\n\n    void Free(T& item)\n    {\n        std::destroy_at(&item);\n        freed.push_back(&item - items.data());\n    }\n\n    void Free(size_t index)\n    {\n        std::destroy_at(&items[index]);\n        freed.push_back(index);\n    }\n\n    size_t Alloc()\n    {\n        if (freed.size())\n        {\n            auto idx = freed[freed.size() - 1];\n            freed.pop_back();\n\n            std::construct_at(&items[idx]);\n            return idx;\n        }\n\n        items.emplace_back();\n        return items.size() - 1;\n    }\n\n    T& operator[](size_t idx)\n    {\n        return items[idx];\n    }\n};"
  },
  {
    "path": "MarathonRecomp/kernel/function.h",
    "content": "#pragma once\n\n#include <cpu/ppc_context.h>\n#include <array>\n#include \"xbox.h\"\n#include \"memory.h\"\n\ntemplate <typename R, typename... T>\nconstexpr std::tuple<T...> function_args(R(*)(T...)) noexcept\n{\n    return std::tuple<T...>();\n}\n\ntemplate<auto V>\nstatic constexpr decltype(V) constant_v = V;\n\ntemplate<typename T>\nstatic constexpr bool is_precise_v = std::is_same_v<T, float> || std::is_same_v<T, double>;\n\ntemplate<auto Func>\nstruct arg_count_t\n{\n    static constexpr size_t value = std::tuple_size_v<decltype(function_args(Func))>;\n};\n\ntemplate<typename TCallable, int I = 0, typename ...TArgs>\nstd::enable_if_t<(I >= sizeof...(TArgs)), void> _tuple_for(std::tuple<TArgs...>&, const TCallable& callable) noexcept\n{\n\n}\n\ntemplate<typename TCallable, int I = 0, typename ...TArgs>\nstd::enable_if_t<(I < sizeof...(TArgs)), void> _tuple_for(std::tuple<TArgs...>& tpl, const TCallable& callable) noexcept\n{\n    callable(std::get<I>(tpl), I);\n\n    _tuple_for<TCallable, I + 1>(tpl, callable);\n}\n\nstruct ArgTranslator\n{\n    constexpr static uint64_t GetIntegerArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept\n    {\n        if (arg <= 7)\n        {\n            switch (arg)\n            {\n                case 0: return ctx.r3.u32;\n                case 1: return ctx.r4.u32;\n                case 2: return ctx.r5.u32;\n                case 3: return ctx.r6.u32;\n                case 4: return ctx.r7.u32;\n                case 5: return ctx.r8.u32;\n                case 6: return ctx.r9.u32;\n                case 7: return ctx.r10.u32;\n                default: break;\n            }\n        }\n\n        return *reinterpret_cast<be<uint32_t>*>(base + ctx.r1.u32 + 0x54 + ((arg - 8) * 8));\n    }\n\n    static double GetPrecisionArgumentValue(const PPCContext& ctx, uint8_t* base, size_t arg) noexcept\n    {\n        switch (arg)\n        {\n            case 0: return ctx.f1.f64;\n            case 1: return ctx.f2.f64;\n            case 2: return ctx.f3.f64;\n            case 3: return ctx.f4.f64;\n            case 4: return ctx.f5.f64;\n            case 5: return ctx.f6.f64;\n            case 6: return ctx.f7.f64;\n            case 7: return ctx.f8.f64;\n            case 8: return ctx.f9.f64;\n            case 9: return ctx.f10.f64;\n            case 10: return ctx.f11.f64;\n            case 11: return ctx.f12.f64;\n            case 12: return ctx.f13.f64;\n            [[unlikely]] default: break;\n        }\n\n        // TODO: get value from stack.\n        return 0;\n    }\n\n    constexpr static void SetIntegerArgumentValue(PPCContext& ctx, uint8_t* base, size_t arg, uint64_t value) noexcept\n    {\n        if (arg <= 7)\n        {\n            switch (arg)\n            {\n                case 0: ctx.r3.u64 = value; return;\n                case 1: ctx.r4.u64 = value; return;\n                case 2: ctx.r5.u64 = value; return;\n                case 3: ctx.r6.u64 = value; return;\n                case 4: ctx.r7.u64 = value; return;\n                case 5: ctx.r8.u64 = value; return;\n                case 6: ctx.r9.u64 = value; return;\n                case 7: ctx.r10.u64 = value; return;\n                [[unlikely]] default: break;\n            }\n        }\n\n        assert(arg < 7 && \"Pushing to stack memory is not yet supported.\");\n    }\n\n    static void SetPrecisionArgumentValue(PPCContext& ctx, uint8_t* base, size_t arg, double value) noexcept\n    {\n        switch (arg)\n        {\n            case 0: ctx.f1.f64 = value; return;\n            case 1: ctx.f2.f64 = value; return;\n            case 2: ctx.f3.f64 = value; return;\n            case 3: ctx.f4.f64 = value; return;\n            case 4: ctx.f5.f64 = value; return;\n            case 5: ctx.f6.f64 = value; return;\n            case 6: ctx.f7.f64 = value; return;\n            case 7: ctx.f8.f64 = value; return;\n            case 8: ctx.f9.f64 = value; return;\n            case 9: ctx.f10.f64 = value; return;\n            case 10: ctx.f11.f64 = value; return;\n            case 11: ctx.f12.f64 = value; return;\n            case 12: ctx.f13.f64 = value; return;\n            [[unlikely]] default: break;\n        }\n\n        assert(arg < 12 && \"Pushing to stack memory is not yet supported.\");\n    }\n\n    template<typename T>\n    constexpr static std::enable_if_t<!std::is_pointer_v<T>, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept\n    {\n        if constexpr (is_precise_v<T>)\n        {\n            return static_cast<T>(GetPrecisionArgumentValue(ctx, base, idx));\n        }\n        else\n        {\n            return static_cast<T>(GetIntegerArgumentValue(ctx, base, idx));\n        }\n    }\n\n    template<typename T>\n    constexpr static std::enable_if_t<std::is_pointer_v<T>, T> GetValue(PPCContext& ctx, uint8_t* base, size_t idx) noexcept\n    {\n        const auto v = GetIntegerArgumentValue(ctx, base, idx);\n        if (!v)\n        {\n            return nullptr;\n        }\n\n        return reinterpret_cast<T>(base + static_cast<uint32_t>(v));\n    }\n\n    template<typename T>\n    constexpr static std::enable_if_t<!std::is_pointer_v<T>, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept\n    {\n        if constexpr (is_precise_v<T>)\n        {\n            SetPrecisionArgumentValue(ctx, base, idx, value);\n        }\n        else if constexpr (std::is_null_pointer_v<T>)\n        {\n            SetIntegerArgumentValue(ctx, base, idx, 0);\n        }\n        else if constexpr (std::is_pointer_v<T>)\n        {\n            SetIntegerArgumentValue(ctx, base, idx, g_memory.MapVirtual(value));\n        }\n        else\n        {\n            SetIntegerArgumentValue(ctx, base, idx, value);\n        }\n    }\n\n    template<typename T>\n    constexpr static std::enable_if_t<std::is_pointer_v<T>, void> SetValue(PPCContext& ctx, uint8_t* base, size_t idx, T value) noexcept\n    {\n        const auto v = g_memory.MapVirtual((void*)value);\n        if (!v)\n        {\n            return;\n        }\n\n        SetValue(ctx, base, idx, v);\n    }\n};\n\nstruct Argument\n{\n    int type{};\n    int ordinal{};\n};\n\ntemplate<typename T1>\nconstexpr std::array<Argument, std::tuple_size_v<T1>> GatherFunctionArguments(const T1& tpl)\n{\n    std::array<Argument, std::tuple_size_v<T1>> args{};\n\n    int floatOrdinal{};\n    size_t i{};\n\n    if constexpr (!args.empty())\n    {\n        std::apply([&](const auto& first, const auto&... rest)\n            {\n                auto append = [&]<typename T2>(const T2& v)\n                {\n                    if constexpr (is_precise_v<T2>)\n                    {\n                        args[i] = { 1, floatOrdinal++ };\n                    }\n                    else\n                    {\n                        args[i] = { 0, static_cast<int>(i) }; // what the fuck\n                    }\n\n                    i++;\n                };\n\n                append(first);\n                (append(rest), ...);\n            }, tpl);\n    }\n\n    return args;\n}\n\ntemplate<auto Func>\nconstexpr std::array<Argument, arg_count_t<Func>::value> GatherFunctionArguments()\n{\n    return GatherFunctionArguments(function_args(Func));\n}\n\ntemplate<auto Func, size_t I>\nstruct arg_ordinal_t\n{\n    static constexpr size_t value = GatherFunctionArguments<Func>()[I].ordinal;\n};\n\ntemplate<auto Func, int I = 0, typename ...TArgs>\nvoid _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>&) noexcept\n    requires (I >= sizeof...(TArgs))\n{\n}\n\ntemplate <auto Func, int I = 0, typename ...TArgs>\nstd::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_host(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>& tpl) noexcept\n{\n    using T = std::tuple_element_t<I, std::remove_reference_t<decltype(tpl)>>;\n    std::get<I>(tpl) = ArgTranslator::GetValue<T>(ctx, base, arg_ordinal_t<Func, I>::value);\n\n    _translate_args_to_host<Func, I + 1>(ctx, base, tpl);\n}\n\ntemplate<int I = 0, typename ...TArgs>\nvoid _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>&) noexcept\n    requires (I >= sizeof...(TArgs))\n{\n}\n\ntemplate <int I = 0, typename ...TArgs>\nstd::enable_if_t<(I < sizeof...(TArgs)), void> _translate_args_to_guest(PPCContext& ctx, uint8_t* base, std::tuple<TArgs...>& tpl) noexcept\n{\n    using T = std::tuple_element_t<I, std::remove_reference_t<decltype(tpl)>>;\n    ArgTranslator::SetValue<T>(ctx, base, GatherFunctionArguments(std::tuple<TArgs...>{})[I].ordinal, std::get<I>(tpl));\n\n    _translate_args_to_guest<I + 1>(ctx, base, tpl);\n}\n\ntemplate<auto Func>\nPPC_FUNC(HostToGuestFunction)\n{\n    using ret_t = decltype(std::apply(Func, function_args(Func)));\n\n    auto args = function_args(Func);\n    _translate_args_to_host<Func>(ctx, base, args);\n\n    if constexpr (std::is_same_v<ret_t, void>)\n    {\n        std::apply(Func, args);\n    }\n    else\n    {\n        auto v = std::apply(Func, args);\n\n        if constexpr (std::is_pointer<ret_t>())\n        {\n            if (v != nullptr)\n            {\n                ctx.r3.u64 = static_cast<uint32_t>(reinterpret_cast<size_t>(v) - reinterpret_cast<size_t>(base));\n            }\n            else\n            {\n                ctx.r3.u64 = 0;\n            }\n        }\n        else if constexpr (is_precise_v<ret_t>)\n        {\n            ctx.f1.f64 = v;\n        }\n        else\n        {\n            ctx.r3.u64 = (uint64_t)v;\n        }\n    }\n}\n\ntemplate<typename T, typename TFunction, typename... TArgs>\nT GuestToHostFunction(const TFunction& func, TArgs&&... argv)\n{\n    auto args = std::make_tuple(std::forward<TArgs>(argv)...);\n    auto& currentCtx = *GetPPCContext();\n\n    PPCContext newCtx; // NOTE: No need for zero initialization, has lots of unnecessary code generation.\n    newCtx.r1 = currentCtx.r1;\n    newCtx.r13 = currentCtx.r13;\n    newCtx.fpscr = currentCtx.fpscr;\n\n    _translate_args_to_guest(newCtx, g_memory.base, args);\n\n    SetPPCContext(newCtx);\n\n    if constexpr (std::is_function_v<TFunction>)\n        func(newCtx, g_memory.base);\n    else\n        g_memory.FindFunction(func)(newCtx, g_memory.base);\n\n    currentCtx.fpscr = newCtx.fpscr;\n    SetPPCContext(currentCtx);\n\n    if constexpr (std::is_pointer_v<T>)\n    {\n        return reinterpret_cast<T>((uint64_t)g_memory.Translate(newCtx.r3.u32));\n    }\n    else if constexpr (is_precise_v<T>)\n    {\n        return static_cast<T>(newCtx.f1.f64);\n    }\n    else if constexpr (std::is_integral_v<T>)\n    {\n        return static_cast<T>(newCtx.r3.u64);\n    }\n    else\n    {\n        static_assert(std::is_void_v<T>, \"Unsupported return type.\");\n    }\n}\n\n#define GUEST_FUNCTION_HOOK(subroutine, function) \\\n    PPC_FUNC(subroutine) { HostToGuestFunction<function>(ctx, base); }\n\n#define GUEST_FUNCTION_STUB(subroutine) \\\n    PPC_FUNC(subroutine) { }\n"
  },
  {
    "path": "MarathonRecomp/kernel/heap.cpp",
    "content": "#include <stdafx.h>\n#include \"heap.h\"\n#include \"memory.h\"\n#include \"function.h\"\n#include \"xdm.h\"\n\nconstexpr size_t RESERVED_BEGIN = 0x7FEA0000;\nconstexpr size_t RESERVED_END = 0xA0000000;\n\nvoid Heap::Init()\n{\n    heap = o1heapInit(g_memory.Translate(0x20000), RESERVED_BEGIN - 0x20000);\n    physicalHeap = o1heapInit(g_memory.Translate(RESERVED_END), 0x100000000 - RESERVED_END);\n}\n\nvoid* Heap::Alloc(size_t size)\n{\n    std::lock_guard lock(mutex);\n\n    return o1heapAllocate(heap, std::max<size_t>(1, size));\n}\n\nvoid* Heap::AllocPhysical(size_t size, size_t alignment)\n{\n    size = std::max<size_t>(1, size);\n    alignment = alignment == 0 ? 0x1000 : std::max<size_t>(16, alignment);\n\n    std::lock_guard lock(physicalMutex);\n\n    void* ptr = o1heapAllocate(physicalHeap, size + alignment);\n    size_t aligned = ((size_t)ptr + alignment) & ~(alignment - 1);\n\n    *((void**)aligned - 1) = ptr;\n    *((size_t*)aligned - 2) = size + O1HEAP_ALIGNMENT;\n\n    return (void*)aligned;\n}\n\nvoid Heap::Free(void* ptr)\n{\n    if (ptr >= physicalHeap)\n    {\n        std::lock_guard lock(physicalMutex);\n        o1heapFree(physicalHeap, *((void**)ptr - 1));\n    }\n    else\n    {\n        std::lock_guard lock(mutex);\n        o1heapFree(heap, ptr);\n    }\n}\n\nsize_t Heap::Size(void* ptr)\n{\n    if (ptr)\n        return *((size_t*)ptr - 2) - O1HEAP_ALIGNMENT; // relies on fragment header in o1heap.c\n\n    return 0;\n}\n\nuint32_t RtlAllocateHeap(uint32_t heapHandle, uint32_t flags, uint32_t size)\n{\n    void* ptr = g_userHeap.Alloc(size);\n    if ((flags & 0x8) != 0)\n        memset(ptr, 0, size);\n\n    assert(ptr);\n    return g_memory.MapVirtual(ptr);\n}\n\nuint32_t RtlReAllocateHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer, uint32_t size)\n{\n    void* ptr = g_userHeap.Alloc(size);\n    if ((flags & 0x8) != 0)\n        memset(ptr, 0, size);\n\n    if (memoryPointer != 0)\n    {\n        void* oldPtr = g_memory.Translate(memoryPointer);\n        memcpy(ptr, oldPtr, std::min<size_t>(size, g_userHeap.Size(oldPtr)));\n        g_userHeap.Free(oldPtr);\n    }\n\n    assert(ptr);\n    return g_memory.MapVirtual(ptr);\n}\n\nuint32_t RtlFreeHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer)\n{\n    if (memoryPointer != NULL)\n        g_userHeap.Free(g_memory.Translate(memoryPointer));\n\n    return true;\n}\n\nuint32_t RtlSizeHeap(uint32_t heapHandle, uint32_t flags, uint32_t memoryPointer)\n{\n    if (memoryPointer != NULL)\n        return (uint32_t)g_userHeap.Size(g_memory.Translate(memoryPointer));\n\n    return 0;\n}\n\nuint32_t XAllocMem(uint32_t size, uint32_t flags)\n{\n    void* ptr = (flags & 0x80000000) != 0 ?\n        g_userHeap.AllocPhysical(size, (1ull << ((flags >> 24) & 0xF))) :\n        g_userHeap.Alloc(size);\n\n    if ((flags & 0x40000000) != 0)\n        memset(ptr, 0, size);\n\n    assert(ptr);\n    return g_memory.MapVirtual(ptr);\n}\n\nvoid XFreeMem(uint32_t baseAddress, uint32_t flags)\n{\n    if (baseAddress != NULL)\n        g_userHeap.Free(g_memory.Translate(baseAddress));\n}\n\nuint32_t XVirtualAlloc(void *lpAddress, unsigned int dwSize, unsigned int flAllocationType, unsigned int flProtect)\n{\n    assert(!lpAddress);\n    return g_memory.MapVirtual(g_userHeap.Alloc(dwSize));\n}\n\nuint32_t XVirtualFree(uint32_t lpAddress, unsigned int dwSize, unsigned int dwFreeType)\n{\n    if ((dwFreeType & 0x8000) != 0 && dwSize)\n        return FALSE;\n\n    if (lpAddress)\n        g_userHeap.Free(g_memory.Translate(lpAddress));\n\n    return TRUE;\n}\n\nGUEST_FUNCTION_HOOK(sub_82915668, XVirtualAlloc);\nGUEST_FUNCTION_HOOK(sub_829156B8, XVirtualFree);\n\nGUEST_FUNCTION_STUB(sub_82535588); // HeapCreate // replaced\n// GUEST_FUNCTION_STUB(sub_82BD9250); // HeapDestroy\n\nGUEST_FUNCTION_HOOK(sub_82535B38, RtlAllocateHeap); // repalced\nGUEST_FUNCTION_HOOK(sub_82536420, RtlFreeHeap); // replaced\nGUEST_FUNCTION_HOOK(sub_82536708, RtlReAllocateHeap); // replaced\nGUEST_FUNCTION_HOOK(sub_82534DD0, RtlSizeHeap); // replaced\n\nGUEST_FUNCTION_HOOK(sub_82537E70, XAllocMem); // replaced\nGUEST_FUNCTION_HOOK(sub_82537F08, XFreeMem); // replaced\n"
  },
  {
    "path": "MarathonRecomp/kernel/heap.h",
    "content": "#pragma once\n\n#include \"mutex.h\"\n\nstruct Heap\n{\n    Mutex mutex;\n    O1HeapInstance* heap;\n\n    Mutex physicalMutex;\n    O1HeapInstance* physicalHeap;\n\n    void Init();\n\n    void* Alloc(size_t size);\n    void* AllocPhysical(size_t size, size_t alignment);\n    void Free(void* ptr);\n\n    size_t Size(void* ptr);\n\n    template<typename T, typename... Args>\n    T* Alloc(Args&&... args)\n    {\n        T* obj = (T*)Alloc(sizeof(T));\n        new (obj) T(std::forward<Args>(args)...);\n        return obj;\n    }\n\n    template<typename T, typename... Args>\n    T* AllocPhysical(Args&&... args)\n    {\n        T* obj = (T*)AllocPhysical(sizeof(T), alignof(T));\n        new (obj) T(std::forward<Args>(args)...);\n        return obj;\n    }\n};\n\nextern Heap g_userHeap;\n"
  },
  {
    "path": "MarathonRecomp/kernel/imports.cpp",
    "content": "#include <cstdint>\n#include <cstdio>\n#include <stdafx.h>\n#include <cpu/ppc_context.h>\n#include <cpu/guest_thread.h>\n#include <apu/audio.h>\n#include \"function.h\"\n#include \"xex.h\"\n#include \"xbox.h\"\n#include \"heap.h\"\n#include \"memory.h\"\n#include <memory>\n#include \"xam.h\"\n#include \"xdm.h\"\n#include <user/config.h>\n#include <os/logger.h>\n\n#ifdef _WIN32\n#include <ntstatus.h>\n#endif\n\nstd::unordered_map<uint32_t, uint32_t> g_handleDuplicates{};\n\nstruct Event final : KernelObject, HostObject<XKEVENT>\n{\n    bool manualReset;\n    std::atomic<bool> signaled;\n\n    Event(XKEVENT* header)\n        : manualReset(!header->Type), signaled(!!header->SignalState)\n    {\n    }\n\n    Event(bool manualReset, bool initialState)\n        : manualReset(manualReset), signaled(initialState)\n    {\n    }\n\n    uint32_t Wait(uint32_t timeout) override\n    {\n        if (timeout == 0)\n        {\n            if (manualReset)\n            {\n                if (!signaled)\n                    return STATUS_TIMEOUT;\n            }\n            else\n            {\n                bool expected = true;\n                if (!signaled.compare_exchange_strong(expected, false))\n                    return STATUS_TIMEOUT;\n            }\n        }\n        else if (timeout == INFINITE)\n        {\n            if (manualReset)\n            {\n                signaled.wait(false);\n            }\n            else\n            {\n                while (true)\n                {\n                    bool expected = true;\n                    if (signaled.compare_exchange_weak(expected, false))\n                        break;\n\n                    signaled.wait(expected);\n                }\n            }\n        }\n        else\n        {\n            assert(false && \"Unhandled timeout value.\");\n        }\n\n        return STATUS_SUCCESS;\n    }\n\n    bool Set()\n    {\n        signaled = true;\n\n        if (manualReset)\n            signaled.notify_all();\n        else\n            signaled.notify_one();\n\n        return TRUE;\n    }\n\n    bool Reset()\n    {\n        signaled = false;\n        return TRUE;\n    }\n};\n\nstatic std::atomic<uint32_t> g_keSetEventGeneration;\n\nstruct Semaphore final : KernelObject, HostObject<XKSEMAPHORE>\n{\n    std::atomic<uint32_t> count;\n    uint32_t maximumCount;\n\n    Semaphore(XKSEMAPHORE* semaphore)\n        : count(semaphore->Header.SignalState), maximumCount(semaphore->Limit)\n    {\n    }\n\n    Semaphore(uint32_t count, uint32_t maximumCount)\n        : count(count), maximumCount(maximumCount)\n    {\n    }\n\n    uint32_t Wait(uint32_t timeout) override\n    {\n        if (timeout == 0)\n        {\n            uint32_t currentCount = count.load();\n            if (currentCount != 0)\n            {\n                if (count.compare_exchange_weak(currentCount, currentCount - 1))\n                    return STATUS_SUCCESS;\n            }\n\n            return STATUS_TIMEOUT;\n        }\n        else if (timeout == INFINITE)\n        {\n            uint32_t currentCount;\n            while (true)\n            {\n                currentCount = count.load();\n                if (currentCount != 0)\n                {\n                    if (count.compare_exchange_weak(currentCount, currentCount - 1))\n                        return STATUS_SUCCESS;\n                }\n                else\n                {\n                    count.wait(0);\n                }\n            }\n\n            return STATUS_SUCCESS;\n        }\n        else\n        {\n            assert(false && \"Unhandled timeout value.\");\n            return STATUS_TIMEOUT;\n        }\n    }\n\n    void Release(uint32_t releaseCount, uint32_t* previousCount)\n    {\n        if (previousCount != nullptr)\n            *previousCount = count;\n\n        assert(count + releaseCount <= maximumCount);\n\n        count += releaseCount;\n        count.notify_all();\n    }\n};\n\ninline void CloseKernelObject(XDISPATCHER_HEADER& header)\n{\n    if (header.WaitListHead.Flink != OBJECT_SIGNATURE)\n    {\n        return;\n    }\n\n    DestroyKernelObject(header.WaitListHead.Blink);\n}\n\nuint32_t GuestTimeoutToMilliseconds(be<int64_t>* timeout)\n{\n    return timeout ? (*timeout * -1) / 10000 : INFINITE;\n}\n\nvoid VdHSIOCalibrationLock()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeCertMonitorData()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XexExecutableModuleHandle()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ExLoadedCommandLine()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeDebugMonitorData()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ExThreadObjectType()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeTimeStampBundle()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XboxHardwareInfo()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XGetVideoMode()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t XGetGameRegion()\n{\n    if (Config::Language == ELanguage::Japanese)\n        return 0x0101;\n\n    return 0x03FF;\n}\n\nuint32_t XMsgStartIORequest(uint32_t App, uint32_t Message, XXOVERLAPPED* lpOverlapped, void* Buffer, uint32_t szBuffer)\n{\n    return STATUS_SUCCESS;\n}\n\nuint32_t XamUserGetSigninState(uint32_t userIndex)\n{\n    return true;\n}\n\nuint32_t XamGetSystemVersion()\n{\n    return 0;\n}\n\nvoid XamContentDelete()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t XamContentGetCreator(uint32_t userIndex, const XCONTENT_DATA* contentData, be<uint32_t>* isCreator, be<uint64_t>* xuid, XXOVERLAPPED* overlapped)\n{\n    if (isCreator)\n        *isCreator = true;\n\n    if (xuid)\n        *xuid = 0xB13EBABEBABEBABE;\n\n    return 0;\n}\n\nuint32_t XamContentGetDeviceState()\n{\n    return 0;\n}\n\nuint32_t XamUserGetSigninInfo(uint32_t userIndex, uint32_t flags, XUSER_SIGNIN_INFO* info)\n{\n    if (userIndex == 0)\n    {\n        memset(info, 0, sizeof(*info));\n        info->xuid = 0xB13EBABEBABEBABE;\n        info->SigninState = 1;\n        strcpy(info->Name, \"SWA\");\n        return 0;\n    }\n\n    return 0x00000525; // ERROR_NO_SUCH_USER\n}\n\nvoid XamShowSigninUI()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t XamShowDeviceSelectorUI\n(\n    uint32_t userIndex,\n    uint32_t contentType,\n    uint32_t contentFlags,\n    uint64_t totalRequested,\n    be<uint32_t>* deviceId,\n    XXOVERLAPPED* overlapped\n)\n{\n    XamNotifyEnqueueEvent(9, true);\n    *deviceId = 1;\n    XamNotifyEnqueueEvent(9, false);\n    return 0;\n}\n\nvoid XamShowDirtyDiscErrorUI()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XamEnableInactivityProcessing()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XamResetInactivity()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XamShowMessageBoxUIEx()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t XGetLanguage()\n{\n    return (uint32_t)Config::Language.Value;\n}\n\nuint32_t XGetAVPack()\n{\n    return 0;\n}\n\nvoid XamLoaderTerminateTitle()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XamGetExecutionId()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XamLoaderLaunchTitle()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtOpenFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlInitAnsiString(XANSI_STRING* destination, char* source)\n{\n    const uint16_t length = source ? (uint16_t)strlen(source) : 0;\n    destination->Length = length;\n    destination->MaximumLength = length + 1;\n    destination->Buffer = source;\n}\n\nuint32_t NtCreateFile\n(\n    be<uint32_t>* FileHandle,\n    uint32_t DesiredAccess,\n    XOBJECT_ATTRIBUTES* Attributes,\n    XIO_STATUS_BLOCK* IoStatusBlock,\n    uint64_t* AllocationSize,\n    uint32_t FileAttributes,\n    uint32_t ShareAccess,\n    uint32_t CreateDisposition,\n    uint32_t CreateOptions\n)\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n    return 0;\n}\n\nuint32_t NtClose(uint32_t handle)\n{\n    if (handle == GUEST_INVALID_HANDLE_VALUE)\n        return 0xFFFFFFFF;\n\n    if (IsKernelObject(handle))\n    {\n        // If the handle was duplicated, just decrement the duplication count. Otherwise, delete the object.\n        const auto& it = g_handleDuplicates.find(handle);\n        if (it == g_handleDuplicates.end() || it->second == 0)\n            DestroyKernelObject(handle);\n        else if (--it->second == 0)\n            g_handleDuplicates.erase(it);\n\n        return 0;\n    }\n    else\n    {\n        assert(false && \"Unrecognized kernel object.\");\n        return 0xFFFFFFFF;\n    }\n}\n\nvoid NtSetInformationFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t FscSetCacheElementCount()\n{\n    return 0;\n}\n\nuint32_t FscGetCacheElementCount()\n{\n    return 0;\n}\n\nuint32_t XamLoaderGetLaunchDataSize()\n{\n    return 0;\n}\n\nuint32_t XamLoaderGetLaunchData()\n{\n    return 0;\n}\n\nuint32_t XamLoaderSetLaunchData()\n{\n    return 0;\n}\n\nuint32_t NtWaitForSingleObjectEx(uint32_t Handle, uint32_t WaitMode, uint32_t Alertable, be<int64_t>* Timeout)\n{\n    if (Handle == GUEST_INVALID_HANDLE_VALUE)\n        return 0xFFFFFFFF;\n\n    uint32_t timeout = GuestTimeoutToMilliseconds(Timeout);\n    // assert(timeout == 0 || timeout == INFINITE);\n\n    if (IsKernelObject(Handle))\n    {\n        return GetKernelObject(Handle)->Wait(timeout);\n    }\n    else\n    {\n        assert(false && \"Unrecognized handle value.\");\n    }\n\n    return STATUS_TIMEOUT;\n}\n\nvoid NtWriteFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid vsprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t ExGetXConfigSetting(uint16_t Category, uint16_t Setting, void* Buffer, uint16_t SizeOfBuffer, be<uint32_t>* RequiredSize)\n{\n    uint32_t data[4]{};\n\n    switch (Category)\n    {\n        // XCONFIG_SECURED_CATEGORY\n        case 0x0002:\n        {\n            switch (Setting)\n            {\n                // XCONFIG_SECURED_AV_REGION\n                case 0x0002:\n                    data[0] = ByteSwap(0x00001000); // USA/Canada\n                    break;\n\n                default:\n                    return 1;\n            }\n        }\n\n        case 0x0003:\n        {\n            switch (Setting)\n            {\n                case 0x0001: // XCONFIG_USER_TIME_ZONE_BIAS\n                case 0x0002: // XCONFIG_USER_TIME_ZONE_STD_NAME\n                case 0x0003: // XCONFIG_USER_TIME_ZONE_DLT_NAME\n                case 0x0004: // XCONFIG_USER_TIME_ZONE_STD_DATE\n                case 0x0005: // XCONFIG_USER_TIME_ZONE_DLT_DATE\n                case 0x0006: // XCONFIG_USER_TIME_ZONE_STD_BIAS\n                case 0x0007: // XCONFIG_USER_TIME_ZONE_DLT_BIAS\n                    data[0] = 0;\n                    break;\n\n                // XCONFIG_USER_LANGUAGE\n                case 0x0009:\n                    data[0] = ByteSwap((uint32_t)Config::Language.Value);\n                    break;\n\n                // XCONFIG_USER_VIDEO_FLAGS\n                case 0x000A:\n                    data[0] = ByteSwap(0x00040000);\n                    break;\n\n                // XCONFIG_USER_RETAIL_FLAGS\n                case 0x000C:\n                    data[0] = ByteSwap(1);\n                    break;\n\n                // XCONFIG_USER_COUNTRY\n                case 0x000E:\n                    data[0] = ByteSwap(103);\n                    break;\n\n                default:\n                    return 1;\n            }\n        }\n    }\n\n    *RequiredSize = 4;\n    memcpy(Buffer, data, std::min((size_t)SizeOfBuffer, sizeof(data)));\n\n    return 0;\n}\n\nvoid NtQueryVirtualMemory()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid MmQueryStatistics()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t NtCreateEvent(be<uint32_t>* handle, void* objAttributes, uint32_t eventType, uint32_t initialState)\n{\n    *handle = GetKernelHandle(CreateKernelObject<Event>(!eventType, !!initialState));\n    return 0;\n}\n\nuint32_t XexCheckExecutablePrivilege()\n{\n    return 0;\n}\n\nvoid DbgPrint()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid __C_specific_handler_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t RtlNtStatusToDosError(uint32_t Status)\n{\n    // See https://github.com/wine-mirror/wine/blob/master/dlls/ntdll/error.c#L47-L64\n    if (Status == 0 || (Status & 0x20000000) != 0)\n        return Status;\n\n    if ((Status & 0xF0000000) == 0xD0000000)\n        Status &= ~0x10000000;\n\n    const uint32_t hi = (Status >> 16) & 0xFFFF;\n    if (hi == 0x8007 || hi == 0xC001 || hi == 0xC007)\n        return Status & 0xFFFF;\n\n    switch (Status)\n    {\n    case uint32_t(STATUS_NOT_IMPLEMENTED):\n        return ERROR_CALL_NOT_IMPLEMENTED;\n    case uint32_t(STATUS_SEMAPHORE_LIMIT_EXCEEDED):\n        return ERROR_TOO_MANY_POSTS;\n    default:\n        LOGF_WARNING(\"Unimplemented NtStatus translation: {:#08x}\", Status);\n        return Status;\n    }\n}\n\nvoid XexGetProcedureAddress()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XexGetModuleSection()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t RtlUnicodeToMultiByteN(char* MultiByteString, uint32_t MaxBytesInMultiByteString, be<uint32_t>* BytesInMultiByteString, const be<uint16_t>* UnicodeString, uint32_t BytesInUnicodeString)\n{\n    const auto reqSize = BytesInUnicodeString / sizeof(uint16_t);\n\n    if (BytesInMultiByteString)\n        *BytesInMultiByteString = reqSize;\n\n    if (reqSize > MaxBytesInMultiByteString)\n        return STATUS_FAIL_CHECK;\n\n    for (size_t i = 0; i < reqSize; i++)\n    {\n        const auto c = UnicodeString[i].get();\n\n        MultiByteString[i] = c < 256 ? c : '?';\n    }\n\n    return STATUS_SUCCESS;\n}\n\nuint32_t KeDelayExecutionThread(uint32_t WaitMode, bool Alertable, be<int64_t>* Timeout)\n{\n    // We don't do async file reads.\n    if (Alertable)\n        return STATUS_USER_APC;\n\n    uint32_t timeout = GuestTimeoutToMilliseconds(Timeout);\n\n#ifdef _WIN32\n    Sleep(timeout);\n#else\n    if (timeout == 0)\n        std::this_thread::yield();\n    else\n        std::this_thread::sleep_for(std::chrono::milliseconds(timeout));\n#endif\n\n    return STATUS_SUCCESS;\n}\n\nvoid ExFreePool()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtQueryInformationFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtQueryVolumeInformationFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtQueryDirectoryFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtReadFileScatter()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtReadFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t NtDuplicateObject(uint32_t SourceHandle, be<uint32_t>* TargetHandle, uint32_t Options)\n{\n    if (SourceHandle == GUEST_INVALID_HANDLE_VALUE)\n        return 0xFFFFFFFF;\n\n    if (IsKernelObject(SourceHandle))\n    {\n        // Increment handle duplicate count.\n        const auto& it = g_handleDuplicates.find(SourceHandle);\n        if (it != g_handleDuplicates.end())\n            ++it->second;\n        else\n            g_handleDuplicates[SourceHandle] = 1;\n\n        *TargetHandle = SourceHandle;\n        return 0;\n    }\n    else\n    {\n        assert(false && \"Unrecognized kernel object.\");\n        return 0xFFFFFFFF;\n    }\n}\n\nvoid NtAllocateVirtualMemory()\n{\n    __builtin_trap();\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtFreeVirtualMemory()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ObDereferenceObject()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeSetBasePriorityThread(GuestThreadHandle* hThread, int priority)\n{\n#ifdef _WIN32\n    if (priority == 16)\n    {\n        priority = 15;\n    }\n    else if (priority == -16)\n    {\n        priority = -15;\n    }\n\n    SetThreadPriority(hThread == GetKernelObject(CURRENT_THREAD_HANDLE) ? GetCurrentThread() : hThread->thread.native_handle(), priority);\n#endif\n}\n\nuint32_t ObReferenceObjectByHandle(uint32_t handle, uint32_t objectType, be<uint32_t>* object)\n{\n    *object = handle;\n    return 0;\n}\n\nvoid KeQueryBasePriorityThread()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t NtSuspendThread(GuestThreadHandle* hThread, uint32_t* suspendCount)\n{\n    assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE) && hThread->GetThreadId() == GuestThread::GetCurrentThreadId());\n\n    hThread->suspended = true;\n    hThread->suspended.wait(true);\n\n    return S_OK;\n}\n\nuint32_t KeSetAffinityThread(uint32_t Thread, uint32_t Affinity, be<uint32_t>* lpPreviousAffinity)\n{\n    if (lpPreviousAffinity)\n        *lpPreviousAffinity = 2;\n\n    return 0;\n}\n\nvoid RtlLeaveCriticalSection(XRTL_CRITICAL_SECTION* cs)\n{\n    // printf(\"RtlLeaveCriticalSection\");\n    cs->RecursionCount = cs->RecursionCount.get() - 1;\n\n    if (cs->RecursionCount.get() != 0)\n        return;\n\n    std::atomic_ref owningThread(cs->OwningThread);\n    owningThread.store(0);\n    owningThread.notify_one();\n}\n\nvoid RtlEnterCriticalSection(XRTL_CRITICAL_SECTION* cs)\n{\n    uint32_t thisThread = g_ppcContext->r13.u32;\n    // printf(\"RtlEnterCriticalSection %x %x %x %x\\n\", thisThread, cs->OwningThread, cs->LockCount, cs->RecursionCount);\n    assert(thisThread != NULL);\n\n    std::atomic_ref owningThread(cs->OwningThread);\n\n    while (true) \n    {\n        uint32_t previousOwner = 0;\n\n        if (owningThread.compare_exchange_weak(previousOwner, thisThread) || previousOwner == thisThread)\n        {\n            cs->RecursionCount = cs->RecursionCount.get() + 1;\n            return;\n        }\n\n        // printf(\"wait start %x\\n\", cs);\n        owningThread.wait(previousOwner);\n        // printf(\"wait end\\n\");\n    }\n}\n\nvoid RtlImageXexHeaderField()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid HalReturnToFirmware()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlFillMemoryUlong()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeBugCheckEx()\n{\n    __builtin_debugtrap();\n}\n\nuint32_t KeGetCurrentProcessType()\n{\n    return 1;\n}\n\nvoid RtlCompareMemoryUlong()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t RtlInitializeCriticalSection(XRTL_CRITICAL_SECTION* cs)\n{\n    // printf(\"RtlInitializeCriticalSection %x\\n\", cs);\n    cs->Header.Absolute = 0;\n    cs->LockCount = -1;\n    cs->RecursionCount = 0;\n    cs->OwningThread = 0;\n\n    return 0;\n}\n\nvoid RtlRaiseException_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KfReleaseSpinLock(uint32_t* spinLock)\n{\n    std::atomic_ref spinLockRef(*spinLock);\n    spinLockRef = 0;\n}\n\nvoid KfAcquireSpinLock(uint32_t* spinLock)\n{\n    std::atomic_ref spinLockRef(*spinLock);\n\n    while (true)\n    {\n        uint32_t expected = 0;\n        if (spinLockRef.compare_exchange_weak(expected, g_ppcContext->r13.u32))\n            break;\n\n        std::this_thread::yield();\n    }\n}\n\nuint64_t KeQueryPerformanceFrequency()\n{\n    return 49875000;\n}\n\nvoid MmFreePhysicalMemory(uint32_t type, uint32_t guestAddress)\n{\n    if (guestAddress != NULL)\n        g_userHeap.Free(g_memory.Translate(guestAddress));\n}\n\nbool VdPersistDisplay(uint32_t a1, uint32_t* a2)\n{\n    *a2 = NULL;\n    return false;\n}\n\nvoid VdSwap()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdGetSystemCommandBuffer()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeReleaseSpinLockFromRaisedIrql(uint32_t* spinLock)\n{\n    std::atomic_ref spinLockRef(*spinLock);\n    spinLockRef = 0;\n}\n\nvoid KeAcquireSpinLockAtRaisedIrql(uint32_t* spinLock)\n{\n    std::atomic_ref spinLockRef(*spinLock);\n\n    while (true)\n    {\n        uint32_t expected = 0;\n        if (spinLockRef.compare_exchange_weak(expected, g_ppcContext->r13.u32))\n            break;\n\n        std::this_thread::yield();\n    }\n}\n\nuint32_t KiApcNormalRoutineNop()\n{\n    return 0;\n}\n\nvoid VdEnableRingBufferRPtrWriteBack()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdInitializeRingBuffer()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t MmGetPhysicalAddress(uint32_t address)\n{\n    LOGF_UTILITY(\"0x{:x}\", address);\n    return address;\n}\n\nvoid VdSetSystemCommandBufferGpuIdentifierAddress()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid _vsnprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid sprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nint32_t ExRegisterTitleTerminateNotification(uint32_t* reg, uint32_t create)\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n    return 0;\n}\n\nvoid VdShutdownEngines()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdQueryVideoMode(XVIDEO_MODE* vm)\n{\n    memset(vm, 0, sizeof(XVIDEO_MODE));\n    vm->DisplayWidth = 1280;\n    vm->DisplayHeight = 720;\n    vm->IsInterlaced = false;\n    vm->IsWidescreen = true;\n    vm->IsHighDefinition = true;\n    vm->RefreshRate = 0x42700000;\n    vm->VideoStandard = 1;\n    vm->Unknown4A = 0x4A;\n    vm->Unknown01 = 0x01;\n}\n\nvoid VdGetCurrentDisplayInformation()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdSetDisplayMode()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdSetGraphicsInterruptCallback()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t VdInitializeEngines()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n    return 1;\n}\n\nvoid VdIsHSIOTrainingSucceeded()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdGetCurrentDisplayGamma()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdQueryVideoFlags()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdCallGraphicsNotificationRoutines()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid VdInitializeScalerCommandBuffer()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeLeaveCriticalRegion()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t VdRetrainEDRAM()\n{\n    return 0;\n}\n\nvoid VdRetrainEDRAMWorker()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeEnterCriticalRegion()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t MmAllocatePhysicalMemoryEx\n(\n    uint32_t flags,\n    uint32_t size,\n    uint32_t protect,\n    uint32_t minAddress,\n    uint32_t maxAddress,\n    uint32_t alignment\n)\n{\n    LOGF_UTILITY(\"0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}, 0x{:x}\", flags, size, protect, minAddress, maxAddress, alignment);\n    return g_memory.MapVirtual(g_userHeap.AllocPhysical(size, alignment));\n}\n\nvoid ObDeleteSymbolicLink()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ObCreateSymbolicLink()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t MmQueryAddressProtect(uint32_t guestAddress)\n{\n    return PAGE_READWRITE;\n}\n\nvoid VdEnableDisableClockGating()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeBugCheck()\n{\n    __builtin_debugtrap();\n}\n\nvoid KeLockL2()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeUnlockL2()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nbool KeSetEvent(XKEVENT* pEvent, uint32_t Increment, bool Wait)\n{\n    bool result = QueryKernelObject<Event>(*pEvent)->Set();\n\n    ++g_keSetEventGeneration;\n    g_keSetEventGeneration.notify_all();\n\n    return result;\n}\n\nbool KeResetEvent(XKEVENT* pEvent)\n{\n    return QueryKernelObject<Event>(*pEvent)->Reset();\n}\n\nuint32_t KeWaitForSingleObject(XDISPATCHER_HEADER* Object, uint32_t WaitReason, uint32_t WaitMode, bool Alertable, be<int64_t>* Timeout)\n{\n    const uint32_t timeout = GuestTimeoutToMilliseconds(Timeout);\n    assert(timeout == INFINITE);\n\n    switch (Object->Type)\n    {\n        case 0:\n        case 1:\n            QueryKernelObject<Event>(*Object)->Wait(timeout);\n            break;\n\n        case 5:\n            QueryKernelObject<Semaphore>(*Object)->Wait(timeout);\n            break;\n\n        default:\n            assert(false && \"Unrecognized kernel object type.\");\n            return STATUS_TIMEOUT;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic std::vector<size_t> g_tlsFreeIndices;\nstatic size_t g_tlsNextIndex = 0;\nstatic Mutex g_tlsAllocationMutex;\n\nstatic uint32_t& KeTlsGetValueRef(size_t index)\n{\n    // Having this a global thread_local variable\n    // for some reason crashes on boot in debug builds.\n    thread_local std::vector<uint32_t> s_tlsValues;\n\n    if (s_tlsValues.size() <= index)\n    {\n        s_tlsValues.resize(index + 1, 0);\n    }\n\n    return s_tlsValues[index];\n}\n\nuint32_t KeTlsGetValue(uint32_t dwTlsIndex)\n{\n    return KeTlsGetValueRef(dwTlsIndex);\n}\n\nuint32_t KeTlsSetValue(uint32_t dwTlsIndex, uint32_t lpTlsValue)\n{\n    KeTlsGetValueRef(dwTlsIndex) = lpTlsValue;\n    return TRUE;\n}\n\nuint32_t KeTlsAlloc()\n{\n    std::lock_guard<Mutex> lock(g_tlsAllocationMutex);\n    if (!g_tlsFreeIndices.empty())\n    {\n        size_t index = g_tlsFreeIndices.back();\n        g_tlsFreeIndices.pop_back();\n        return index;\n    }\n\n    return g_tlsNextIndex++;\n}\n\nuint32_t KeTlsFree(uint32_t dwTlsIndex)\n{\n    std::lock_guard<Mutex> lock(g_tlsAllocationMutex);\n    g_tlsFreeIndices.push_back(dwTlsIndex);\n    return TRUE;\n}\n\nuint32_t XMsgInProcessCall(uint32_t app, uint32_t message, be<uint32_t>* param1, be<uint32_t>* param2)\n{\n    if (message == 0x7001B)\n    {\n        uint32_t* ptr = (uint32_t*)g_memory.Translate(param1[1]);\n        ptr[0] = 0;\n        ptr[1] = 0;\n    }\n\n    return 0;\n}\n\nvoid XamUserReadProfileSettings\n(\n    uint32_t titleId,\n    uint32_t userIndex,\n    uint32_t xuidCount,\n    uint64_t* xuids,\n    uint32_t settingCount,\n    uint32_t* settingIds,\n    be<uint32_t>* bufferSize,\n    void* buffer,\n    void* overlapped\n)\n{\n    if (buffer != nullptr)\n    {\n        memset(buffer, 0, *bufferSize);\n    }\n    else\n    {\n        *bufferSize = 4;\n    }\n}\n\nvoid NetDll_WSAStartup()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_WSACleanup()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_socket()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_closesocket()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_setsockopt()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_bind()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_connect()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_listen()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_accept()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_select()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_recv()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_send()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_inet_addr()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll___WSAFDIsSet()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XMsgStartIORequestEx()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XexGetModuleHandle()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nbool RtlTryEnterCriticalSection(XRTL_CRITICAL_SECTION* cs)\n{\n    // printf(\"RtlTryEnterCriticalSection\\n\");\n    uint32_t thisThread = g_ppcContext->r13.u32;\n    assert(thisThread != NULL);\n\n    std::atomic_ref owningThread(cs->OwningThread);\n\n    uint32_t previousOwner = 0;\n\n    if (owningThread.compare_exchange_weak(previousOwner, thisThread) || previousOwner == thisThread)\n    {\n        cs->RecursionCount = cs->RecursionCount.get() + 1;\n        return true;\n    }\n\n    return false;\n}\n\nvoid RtlInitializeCriticalSectionAndSpinCount(XRTL_CRITICAL_SECTION* cs, uint32_t spinCount)\n{\n    // printf(\"RtlInitializeCriticalSectionAndSpinCount\\n\");\n    cs->Header.Absolute = (spinCount + 255) >> 8;\n    cs->LockCount = -1;\n    cs->RecursionCount = 0;\n    cs->OwningThread = 0;\n}\n\nvoid _vswprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid _vscwprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid _swprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid _snwprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XeCryptBnQwBeSigVerify()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XeKeysGetKey()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XeCryptRotSumSha()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XeCryptSha()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeEnableFpuExceptions()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlUnwind_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlCaptureContext_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtQueryFullAttributesFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t RtlMultiByteToUnicodeN(be<uint16_t>* UnicodeString, uint32_t MaxBytesInUnicodeString, be<uint32_t>* BytesInUnicodeString, const char* MultiByteString, uint32_t BytesInMultiByteString)\n{\n    uint32_t length = std::min(MaxBytesInUnicodeString / 2, BytesInMultiByteString);\n\n    for (size_t i = 0; i < length; i++)\n        UnicodeString[i] = MultiByteString[i];\n\n    if (BytesInUnicodeString != nullptr)\n        *BytesInUnicodeString = length * 2;\n\n    return STATUS_SUCCESS;\n}\n\nvoid DbgBreakPoint()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid MmQueryAllocationSize()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t NtClearEvent(Event* handle, uint32_t* previousState)\n{\n    handle->Reset();\n    return 0;\n}\n\nuint32_t NtResumeThread(GuestThreadHandle* hThread, uint32_t* suspendCount)\n{\n    assert(hThread != GetKernelObject(CURRENT_THREAD_HANDLE));\n\n    hThread->suspended = false;\n    hThread->suspended.notify_all();\n\n    return S_OK;\n}\n\nuint32_t NtSetEvent(Event* handle, uint32_t* previousState)\n{\n    handle->Set();\n    return 0;\n}\n\nuint32_t NtCreateSemaphore(be<uint32_t>* Handle, XOBJECT_ATTRIBUTES* ObjectAttributes, uint32_t InitialCount, uint32_t MaximumCount)\n{\n    *Handle = GetKernelHandle(CreateKernelObject<Semaphore>(InitialCount, MaximumCount));\n    return STATUS_SUCCESS;\n}\n\nuint32_t NtReleaseSemaphore(Semaphore* Handle, uint32_t ReleaseCount, int32_t* PreviousCount)\n{\n    // the game releases semaphore with 1 maximum number of releases more than once\n    if (Handle->count + ReleaseCount > Handle->maximumCount)\n        return STATUS_SEMAPHORE_LIMIT_EXCEEDED;\n\n    uint32_t previousCount;\n    Handle->Release(ReleaseCount, &previousCount);\n\n    if (PreviousCount != nullptr)\n        *PreviousCount = ByteSwap(previousCount);\n\n    return STATUS_SUCCESS;\n}\n\nvoid NtWaitForMultipleObjectsEx()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlCompareStringN()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid _snprintf_x()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid StfsControlDevice()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid StfsCreateDevice()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NtFlushBuffersFile()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid KeQuerySystemTime(be<uint64_t>* time)\n{\n    constexpr int64_t FILETIME_EPOCH_DIFFERENCE = 116444736000000000LL;\n\n    auto now = std::chrono::system_clock::now();\n    auto timeSinceEpoch = now.time_since_epoch();\n\n    int64_t currentTime100ns = std::chrono::duration_cast<std::chrono::duration<int64_t, std::ratio<1, 10000000>>>(timeSinceEpoch).count();\n    currentTime100ns += FILETIME_EPOCH_DIFFERENCE;\n\n    *time = currentTime100ns;\n}\n\nstruct TIME_FIELDS {\n    be<uint16_t> Year;\n    be<uint16_t> Month;\n    be<uint16_t> Day;\n    be<uint16_t> Hour;\n    be<uint16_t> Minute;\n    be<uint16_t> Second;\n    be<uint16_t> Milliseconds;\n    be<uint16_t> Weekday;\n};\n\nvoid RtlTimeToTimeFields(const be<uint64_t>* time, TIME_FIELDS* timeFields)\n{\n    constexpr uint64_t TICKS_PER_MILLISECOND = 10000;\n    constexpr uint64_t TICKS_PER_SECOND = 10000000;\n    constexpr uint64_t TICKS_PER_MINUTE = 600000000;\n    constexpr uint64_t TICKS_PER_HOUR = 36000000000;\n    constexpr uint64_t TICKS_PER_DAY = 864000000000;\n\n    static const int DaysInMonth[2][12] = {\n            {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, // Non-leap\n            {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}  // Leap\n    };\n\n    // Calculate total days since January 1, 1601\n    uint64_t days = *time / TICKS_PER_DAY;\n    uint64_t remainingTicks = *time % TICKS_PER_DAY;\n\n    timeFields->Hour = static_cast<uint16_t>(remainingTicks / TICKS_PER_HOUR);\n    remainingTicks %= TICKS_PER_HOUR;\n\n    timeFields->Minute = static_cast<uint16_t>(remainingTicks / TICKS_PER_MINUTE);\n    remainingTicks %= TICKS_PER_MINUTE;\n\n    timeFields->Second = static_cast<uint16_t>(remainingTicks / TICKS_PER_SECOND);\n    remainingTicks %= TICKS_PER_SECOND;\n\n    timeFields->Milliseconds = static_cast<uint16_t>(remainingTicks / TICKS_PER_MILLISECOND);\n\n    // Calculate day of week (January 1, 1601 was a Monday = 1)\n    timeFields->Weekday = static_cast<uint16_t>((days + 1) % 7);\n\n    // Calculate year\n    uint32_t year = 1601;\n\n    // Each 400-year cycle has 146097 days\n    uint32_t cycles400 = static_cast<uint32_t>(days / 146097);\n    days %= 146097;\n    year += cycles400 * 400;\n\n    // Handle 100-year cycles (24 leap years + 76 normal years = 36524 days)\n    // Except the 4th century which has 36525 days\n    uint32_t cycles100 = static_cast<uint32_t>(days / 36524);\n    if (cycles100 == 4) cycles100 = 3; // Last day of 400-year cycle\n    days -= cycles100 * 36524;\n    year += cycles100 * 100;\n\n    // Handle 4-year cycles (1 leap year + 3 normal years = 1461 days)\n    uint32_t cycles4 = static_cast<uint32_t>(days / 1461);\n    days %= 1461;\n    year += cycles4 * 4;\n\n    // Handle individual years within 4-year cycle\n    uint32_t yearInCycle = static_cast<uint32_t>(days / 365);\n    if (yearInCycle == 4) yearInCycle = 3; // Last day of leap cycle\n    days -= yearInCycle * 365;\n    if (yearInCycle > 0) {\n        // Account for leap days in previous years of this cycle\n        days -= (yearInCycle - 1) / 4;\n    }\n    year += yearInCycle;\n\n    timeFields->Year = static_cast<uint16_t>(year);\n\n    // Determine if current year is a leap year\n    bool isLeapYear = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);\n\n    // Calculate month and day\n    const int* monthDays = DaysInMonth[isLeapYear ? 1 : 0];\n    uint32_t dayOfYear = static_cast<uint32_t>(days) + 1; // Convert to 1-based\n\n    uint16_t month = 1;\n    while (dayOfYear > static_cast<uint32_t>(monthDays[month - 1])) {\n        dayOfYear -= monthDays[month - 1];\n        month++;\n    }\n\n    timeFields->Month = month;\n    timeFields->Day = static_cast<uint16_t>(dayOfYear);\n}\n\nvoid RtlFreeAnsiString()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlUnicodeStringToAnsiString()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlInitUnicodeString()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ExTerminateThread()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t ExCreateThread(be<uint32_t>* handle, uint32_t stackSize, be<uint32_t>* threadId, uint32_t xApiThreadStartup, uint32_t startAddress, uint32_t startContext, uint32_t creationFlags)\n{\n\n    uint32_t hostThreadId;\n\n    *handle = GetKernelHandle(GuestThread::Start({ startAddress, startContext, creationFlags }, &hostThreadId));\n    LOGF_UTILITY(\"0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X}, 0x{:X} {:X}\",\n        (intptr_t)handle, stackSize, (intptr_t)threadId, xApiThreadStartup, startAddress, startContext, creationFlags, hostThreadId);\n    if (threadId != nullptr)\n        *threadId = hostThreadId;\n\n    return 0;\n}\n\nvoid IoInvalidDeviceRequest()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ObReferenceObject()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid IoCreateDevice()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid IoDeleteDevice()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ExAllocatePoolTypeWithTag()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlTimeFieldsToTime()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid IoCompleteRequest()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid RtlUpcaseUnicodeChar()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid ObIsTitleObject()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid IoCheckShareAccess()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid IoSetShareAccess()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid IoRemoveShareAccess()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_XNetStartup()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid NetDll_XNetGetTitleXnAddr()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t KeWaitForMultipleObjects(uint32_t Count, xpointer<XDISPATCHER_HEADER>* Objects, uint32_t WaitType, uint32_t WaitReason, uint32_t WaitMode, uint32_t Alertable, be<int64_t>* Timeout)\n{\n    // FIXME: This function is only accounting for events.\n\n    const uint64_t timeout = GuestTimeoutToMilliseconds(Timeout);\n    assert(timeout == INFINITE);\n\n    if (WaitType == 0) // Wait all\n    {\n        for (size_t i = 0; i < Count; i++)\n            QueryKernelObject<Event>(*Objects[i])->Wait(timeout);\n    }\n    else\n    {\n        thread_local std::vector<Event*> s_events;\n        s_events.resize(Count);\n\n        for (size_t i = 0; i < Count; i++)\n            s_events[i] = QueryKernelObject<Event>(*Objects[i]);\n\n        while (true)\n        {\n            uint32_t generation = g_keSetEventGeneration.load();\n\n            for (size_t i = 0; i < Count; i++)\n            {\n                if (s_events[i]->Wait(0) == STATUS_SUCCESS)\n                {\n                    return STATUS_WAIT_0 + i;\n                }\n            }\n\n            g_keSetEventGeneration.wait(generation);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nuint32_t KeRaiseIrqlToDpcLevel()\n{\n    return 0;\n}\n\nvoid KfLowerIrql() { }\n\nuint32_t KeReleaseSemaphore(XKSEMAPHORE* semaphore, uint32_t increment, uint32_t adjustment, uint32_t wait)\n{\n    auto* object = QueryKernelObject<Semaphore>(semaphore->Header);\n    object->Release(adjustment, nullptr);\n    return STATUS_SUCCESS;\n}\n\nvoid XAudioGetVoiceCategoryVolume()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nuint32_t XAudioGetVoiceCategoryVolumeChangeMask(uint32_t Driver, be<uint32_t>* Mask)\n{\n    *Mask = 0;\n    return 0;\n}\n\nuint32_t KeResumeThread(GuestThreadHandle* object)\n{\n    assert(object != GetKernelObject(CURRENT_THREAD_HANDLE));\n\n    object->suspended = false;\n    object->suspended.notify_all();\n    return 0;\n}\n\nvoid KeInitializeSemaphore(XKSEMAPHORE* semaphore, uint32_t count, uint32_t limit)\n{\n    semaphore->Header.Type = 5;\n    semaphore->Header.SignalState = count;\n    semaphore->Limit = limit;\n\n    auto* object = QueryKernelObject<Semaphore>(semaphore->Header);\n}\n\nvoid XMAReleaseContext()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\nvoid XMACreateContext()\n{\n    LOG_UTILITY(\"!!! STUB !!!\");\n}\n\n// uint32_t XAudioRegisterRenderDriverClient(be<uint32_t>* callback, be<uint32_t>* driver)\n// {\n//     //printf(\"XAudioRegisterRenderDriverClient(): %x %x\\n\");\n// \n//     *driver = apu::RegisterClient(callback[0], callback[1]);\n//     return 0;\n// }\n\n// void XAudioUnregisterRenderDriverClient()\n// {\n//     printf(\"!!! STUB !!! XAudioUnregisterRenderDriverClient\\n\");\n// }\n\n// uint32_t XAudioSubmitRenderDriverFrame(uint32_t driver, void* samples)\n// {\n//     // printf(\"!!! STUB !!! XAudioSubmitRenderDriverFrame\\n\");\n//     apu::SubmitFrames(samples);\n// \n//     return 0;\n// }\n\nvoid XapiInitProcess()\n{\n    printf(\"XapiInitProcess Invoked\\n\");\n\n    int *XapiProcessHeap = (int *)g_memory.Translate(0x82D57540);\n\n    *XapiProcessHeap = 1;\n}\n\nGUEST_FUNCTION_HOOK(sub_825383D8, XapiInitProcess)\nGUEST_FUNCTION_HOOK(__imp__XGetVideoMode, VdQueryVideoMode); // XGetVideoMode\nGUEST_FUNCTION_HOOK(__imp__XNotifyGetNext, XNotifyGetNext);\nGUEST_FUNCTION_HOOK(__imp__XGetGameRegion, XGetGameRegion);\nGUEST_FUNCTION_HOOK(__imp__XMsgStartIORequest, XMsgStartIORequest);\nGUEST_FUNCTION_HOOK(__imp__XamUserGetSigninState, XamUserGetSigninState);\nGUEST_FUNCTION_HOOK(__imp__XamGetSystemVersion, XamGetSystemVersion);\nGUEST_FUNCTION_HOOK(__imp__XamContentCreateEx, XamContentCreateEx);\nGUEST_FUNCTION_HOOK(__imp__XamContentDelete, XamContentDelete);\nGUEST_FUNCTION_HOOK(__imp__XamContentClose, XamContentClose);\nGUEST_FUNCTION_HOOK(__imp__XamContentGetCreator, XamContentGetCreator);\nGUEST_FUNCTION_HOOK(__imp__XamContentCreateEnumerator, XamContentCreateEnumerator);\nGUEST_FUNCTION_HOOK(__imp__XamContentGetDeviceState, XamContentGetDeviceState);\nGUEST_FUNCTION_HOOK(__imp__XamContentGetDeviceData, XamContentGetDeviceData);\nGUEST_FUNCTION_HOOK(__imp__XamEnumerate, XamEnumerate);\nGUEST_FUNCTION_HOOK(__imp__XamNotifyCreateListener, XamNotifyCreateListener);\nGUEST_FUNCTION_HOOK(__imp__XamUserGetSigninInfo, XamUserGetSigninInfo);\nGUEST_FUNCTION_HOOK(__imp__XamShowSigninUI, XamShowSigninUI);\nGUEST_FUNCTION_HOOK(__imp__XamShowDeviceSelectorUI, XamShowDeviceSelectorUI);\nGUEST_FUNCTION_HOOK(__imp__XamShowMessageBoxUI, XamShowMessageBoxUI);\nGUEST_FUNCTION_HOOK(__imp__XamShowDirtyDiscErrorUI, XamShowDirtyDiscErrorUI);\nGUEST_FUNCTION_HOOK(__imp__XamEnableInactivityProcessing, XamEnableInactivityProcessing);\nGUEST_FUNCTION_HOOK(__imp__XamResetInactivity, XamResetInactivity);\nGUEST_FUNCTION_HOOK(__imp__XamShowMessageBoxUIEx, XamShowMessageBoxUIEx);\nGUEST_FUNCTION_HOOK(__imp__XGetLanguage, XGetLanguage);\nGUEST_FUNCTION_HOOK(__imp__XGetAVPack, XGetAVPack);\nGUEST_FUNCTION_HOOK(__imp__XamLoaderTerminateTitle, XamLoaderTerminateTitle);\nGUEST_FUNCTION_HOOK(__imp__XamGetExecutionId, XamGetExecutionId);\nGUEST_FUNCTION_HOOK(__imp__XamLoaderLaunchTitle, XamLoaderLaunchTitle);\nGUEST_FUNCTION_HOOK(__imp__NtOpenFile, NtOpenFile);\nGUEST_FUNCTION_HOOK(__imp__RtlInitAnsiString, RtlInitAnsiString);\nGUEST_FUNCTION_HOOK(__imp__NtCreateFile, NtCreateFile);\nGUEST_FUNCTION_HOOK(__imp__NtClose, NtClose);\nGUEST_FUNCTION_HOOK(__imp__NtSetInformationFile, NtSetInformationFile);\nGUEST_FUNCTION_HOOK(__imp__FscGetCacheElementCount, FscGetCacheElementCount);\nGUEST_FUNCTION_HOOK(__imp__FscSetCacheElementCount, FscSetCacheElementCount);\nGUEST_FUNCTION_HOOK(__imp__XamLoaderGetLaunchDataSize, XamLoaderGetLaunchDataSize);\nGUEST_FUNCTION_HOOK(__imp__XamLoaderGetLaunchData, XamLoaderGetLaunchData);\nGUEST_FUNCTION_HOOK(__imp__XamLoaderSetLaunchData, XamLoaderSetLaunchData);\nGUEST_FUNCTION_HOOK(__imp__NtWaitForSingleObjectEx, NtWaitForSingleObjectEx);\nGUEST_FUNCTION_HOOK(__imp__NtWriteFile, NtWriteFile);\nGUEST_FUNCTION_HOOK(__imp__ExGetXConfigSetting, ExGetXConfigSetting);\nGUEST_FUNCTION_HOOK(__imp__NtQueryVirtualMemory, NtQueryVirtualMemory);\nGUEST_FUNCTION_HOOK(__imp__MmQueryStatistics, MmQueryStatistics);\nGUEST_FUNCTION_HOOK(__imp__NtCreateEvent, NtCreateEvent);\nGUEST_FUNCTION_HOOK(__imp__XexCheckExecutablePrivilege, XexCheckExecutablePrivilege);\nGUEST_FUNCTION_HOOK(__imp__DbgPrint, DbgPrint);\nGUEST_FUNCTION_HOOK(__imp____C_specific_handler, __C_specific_handler_x);\nGUEST_FUNCTION_HOOK(__imp__RtlNtStatusToDosError, RtlNtStatusToDosError);\nGUEST_FUNCTION_HOOK(__imp__XexGetProcedureAddress, XexGetProcedureAddress);\nGUEST_FUNCTION_HOOK(__imp__XexGetModuleSection, XexGetModuleSection);\nGUEST_FUNCTION_HOOK(__imp__RtlUnicodeToMultiByteN, RtlUnicodeToMultiByteN);\nGUEST_FUNCTION_HOOK(__imp__KeDelayExecutionThread, KeDelayExecutionThread);\nGUEST_FUNCTION_HOOK(__imp__ExFreePool, ExFreePool);\nGUEST_FUNCTION_HOOK(__imp__NtQueryInformationFile, NtQueryInformationFile);\nGUEST_FUNCTION_HOOK(__imp__NtQueryVolumeInformationFile, NtQueryVolumeInformationFile);\nGUEST_FUNCTION_HOOK(__imp__NtQueryDirectoryFile, NtQueryDirectoryFile);\nGUEST_FUNCTION_HOOK(__imp__NtReadFileScatter, NtReadFileScatter);\nGUEST_FUNCTION_HOOK(__imp__NtReadFile, NtReadFile);\nGUEST_FUNCTION_HOOK(__imp__NtDuplicateObject, NtDuplicateObject);\nGUEST_FUNCTION_HOOK(__imp__NtAllocateVirtualMemory, NtAllocateVirtualMemory);\nGUEST_FUNCTION_HOOK(__imp__NtFreeVirtualMemory, NtFreeVirtualMemory);\nGUEST_FUNCTION_HOOK(__imp__ObDereferenceObject, ObDereferenceObject);\nGUEST_FUNCTION_HOOK(__imp__KeSetBasePriorityThread, KeSetBasePriorityThread);\nGUEST_FUNCTION_HOOK(__imp__ObReferenceObjectByHandle, ObReferenceObjectByHandle);\nGUEST_FUNCTION_HOOK(__imp__KeQueryBasePriorityThread, KeQueryBasePriorityThread);\nGUEST_FUNCTION_HOOK(__imp__NtSuspendThread, NtSuspendThread);\nGUEST_FUNCTION_HOOK(__imp__KeSetAffinityThread, KeSetAffinityThread);\nGUEST_FUNCTION_HOOK(__imp__RtlLeaveCriticalSection, RtlLeaveCriticalSection);\nGUEST_FUNCTION_HOOK(__imp__RtlEnterCriticalSection, RtlEnterCriticalSection);\nGUEST_FUNCTION_HOOK(__imp__RtlImageXexHeaderField, RtlImageXexHeaderField);\nGUEST_FUNCTION_HOOK(__imp__HalReturnToFirmware, HalReturnToFirmware);\nGUEST_FUNCTION_HOOK(__imp__RtlFillMemoryUlong, RtlFillMemoryUlong);\nGUEST_FUNCTION_HOOK(__imp__KeBugCheckEx, KeBugCheckEx);\nGUEST_FUNCTION_HOOK(__imp__KeGetCurrentProcessType, KeGetCurrentProcessType);\nGUEST_FUNCTION_HOOK(__imp__RtlCompareMemoryUlong, RtlCompareMemoryUlong);\nGUEST_FUNCTION_HOOK(__imp__RtlInitializeCriticalSection, RtlInitializeCriticalSection);\nGUEST_FUNCTION_HOOK(__imp__RtlRaiseException, RtlRaiseException_x);\nGUEST_FUNCTION_HOOK(__imp__KfReleaseSpinLock, KfReleaseSpinLock);\nGUEST_FUNCTION_HOOK(__imp__KfAcquireSpinLock, KfAcquireSpinLock);\nGUEST_FUNCTION_HOOK(__imp__KeQueryPerformanceFrequency, KeQueryPerformanceFrequency);\nGUEST_FUNCTION_HOOK(__imp__MmFreePhysicalMemory, MmFreePhysicalMemory);\nGUEST_FUNCTION_HOOK(__imp__VdPersistDisplay, VdPersistDisplay);\nGUEST_FUNCTION_HOOK(__imp__VdSwap, VdSwap);\nGUEST_FUNCTION_HOOK(__imp__VdGetSystemCommandBuffer, VdGetSystemCommandBuffer);\nGUEST_FUNCTION_HOOK(__imp__KeReleaseSpinLockFromRaisedIrql, KeReleaseSpinLockFromRaisedIrql);\nGUEST_FUNCTION_HOOK(__imp__KeAcquireSpinLockAtRaisedIrql, KeAcquireSpinLockAtRaisedIrql);\nGUEST_FUNCTION_HOOK(__imp__KiApcNormalRoutineNop, KiApcNormalRoutineNop);\nGUEST_FUNCTION_HOOK(__imp__VdEnableRingBufferRPtrWriteBack, VdEnableRingBufferRPtrWriteBack);\nGUEST_FUNCTION_HOOK(__imp__VdInitializeRingBuffer, VdInitializeRingBuffer);\nGUEST_FUNCTION_HOOK(__imp__MmGetPhysicalAddress, MmGetPhysicalAddress);\nGUEST_FUNCTION_HOOK(__imp__VdSetSystemCommandBufferGpuIdentifierAddress, VdSetSystemCommandBufferGpuIdentifierAddress);\nGUEST_FUNCTION_HOOK(__imp__ExRegisterTitleTerminateNotification, ExRegisterTitleTerminateNotification);\nGUEST_FUNCTION_HOOK(__imp__VdShutdownEngines, VdShutdownEngines);\nGUEST_FUNCTION_HOOK(__imp__VdQueryVideoMode, VdQueryVideoMode);\nGUEST_FUNCTION_HOOK(__imp__VdGetCurrentDisplayInformation, VdGetCurrentDisplayInformation);\nGUEST_FUNCTION_HOOK(__imp__VdSetDisplayMode, VdSetDisplayMode);\nGUEST_FUNCTION_HOOK(__imp__VdSetGraphicsInterruptCallback, VdSetGraphicsInterruptCallback);\nGUEST_FUNCTION_HOOK(__imp__VdInitializeEngines, VdInitializeEngines);\nGUEST_FUNCTION_HOOK(__imp__VdIsHSIOTrainingSucceeded, VdIsHSIOTrainingSucceeded);\nGUEST_FUNCTION_HOOK(__imp__VdGetCurrentDisplayGamma, VdGetCurrentDisplayGamma);\nGUEST_FUNCTION_HOOK(__imp__VdQueryVideoFlags, VdQueryVideoFlags);\nGUEST_FUNCTION_HOOK(__imp__VdCallGraphicsNotificationRoutines, VdCallGraphicsNotificationRoutines);\nGUEST_FUNCTION_HOOK(__imp__VdInitializeScalerCommandBuffer, VdInitializeScalerCommandBuffer);\nGUEST_FUNCTION_HOOK(__imp__KeLeaveCriticalRegion, KeLeaveCriticalRegion);\nGUEST_FUNCTION_HOOK(__imp__VdRetrainEDRAM, VdRetrainEDRAM);\nGUEST_FUNCTION_HOOK(__imp__VdRetrainEDRAMWorker, VdRetrainEDRAMWorker);\nGUEST_FUNCTION_HOOK(__imp__KeEnterCriticalRegion, KeEnterCriticalRegion);\nGUEST_FUNCTION_HOOK(__imp__MmAllocatePhysicalMemoryEx, MmAllocatePhysicalMemoryEx);\nGUEST_FUNCTION_HOOK(__imp__ObDeleteSymbolicLink, ObDeleteSymbolicLink);\nGUEST_FUNCTION_HOOK(__imp__ObCreateSymbolicLink, ObCreateSymbolicLink);\nGUEST_FUNCTION_HOOK(__imp__MmQueryAddressProtect, MmQueryAddressProtect);\nGUEST_FUNCTION_HOOK(__imp__VdEnableDisableClockGating, VdEnableDisableClockGating);\nGUEST_FUNCTION_HOOK(__imp__KeBugCheck, KeBugCheck);\nGUEST_FUNCTION_HOOK(__imp__KeLockL2, KeLockL2);\nGUEST_FUNCTION_HOOK(__imp__KeUnlockL2, KeUnlockL2);\nGUEST_FUNCTION_HOOK(__imp__KeSetEvent, KeSetEvent);\nGUEST_FUNCTION_HOOK(__imp__KeResetEvent, KeResetEvent);\nGUEST_FUNCTION_HOOK(__imp__KeWaitForSingleObject, KeWaitForSingleObject);\nGUEST_FUNCTION_HOOK(__imp__KeTlsGetValue, KeTlsGetValue);\nGUEST_FUNCTION_HOOK(__imp__KeTlsSetValue, KeTlsSetValue);\nGUEST_FUNCTION_HOOK(__imp__KeTlsAlloc, KeTlsAlloc);\nGUEST_FUNCTION_HOOK(__imp__KeTlsFree, KeTlsFree);\nGUEST_FUNCTION_HOOK(__imp__XMsgInProcessCall, XMsgInProcessCall);\nGUEST_FUNCTION_HOOK(__imp__XamUserReadProfileSettings, XamUserReadProfileSettings);\nGUEST_FUNCTION_HOOK(__imp__NetDll_WSAStartup, NetDll_WSAStartup);\nGUEST_FUNCTION_HOOK(__imp__NetDll_WSACleanup, NetDll_WSACleanup);\nGUEST_FUNCTION_HOOK(__imp__NetDll_socket, NetDll_socket);\nGUEST_FUNCTION_HOOK(__imp__NetDll_closesocket, NetDll_closesocket);\nGUEST_FUNCTION_HOOK(__imp__NetDll_setsockopt, NetDll_setsockopt);\nGUEST_FUNCTION_HOOK(__imp__NetDll_bind, NetDll_bind);\nGUEST_FUNCTION_HOOK(__imp__NetDll_connect, NetDll_connect);\nGUEST_FUNCTION_HOOK(__imp__NetDll_listen, NetDll_listen);\nGUEST_FUNCTION_HOOK(__imp__NetDll_accept, NetDll_accept);\nGUEST_FUNCTION_HOOK(__imp__NetDll_select, NetDll_select);\nGUEST_FUNCTION_HOOK(__imp__NetDll_recv, NetDll_recv);\nGUEST_FUNCTION_HOOK(__imp__NetDll_send, NetDll_send);\nGUEST_FUNCTION_HOOK(__imp__NetDll_inet_addr, NetDll_inet_addr);\nGUEST_FUNCTION_HOOK(__imp__NetDll___WSAFDIsSet, NetDll___WSAFDIsSet);\nGUEST_FUNCTION_HOOK(__imp__XMsgStartIORequestEx, XMsgStartIORequestEx);\nGUEST_FUNCTION_HOOK(__imp__XamInputGetCapabilities, XamInputGetCapabilities);\nGUEST_FUNCTION_HOOK(__imp__XamInputGetState, XamInputGetState);\nGUEST_FUNCTION_HOOK(__imp__XamInputSetState, XamInputSetState);\nGUEST_FUNCTION_HOOK(__imp__XexGetModuleHandle, XexGetModuleHandle);\nGUEST_FUNCTION_HOOK(__imp__RtlTryEnterCriticalSection, RtlTryEnterCriticalSection);\nGUEST_FUNCTION_HOOK(__imp__RtlInitializeCriticalSectionAndSpinCount, RtlInitializeCriticalSectionAndSpinCount);\nGUEST_FUNCTION_HOOK(__imp__XeCryptBnQwBeSigVerify, XeCryptBnQwBeSigVerify);\nGUEST_FUNCTION_HOOK(__imp__XeKeysGetKey, XeKeysGetKey);\nGUEST_FUNCTION_HOOK(__imp__XeCryptRotSumSha, XeCryptRotSumSha);\nGUEST_FUNCTION_HOOK(__imp__XeCryptSha, XeCryptSha);\nGUEST_FUNCTION_HOOK(__imp__KeEnableFpuExceptions, KeEnableFpuExceptions);\nGUEST_FUNCTION_HOOK(__imp__RtlUnwind, RtlUnwind_x);\nGUEST_FUNCTION_HOOK(__imp__RtlCaptureContext, RtlCaptureContext_x);\nGUEST_FUNCTION_HOOK(__imp__NtQueryFullAttributesFile, NtQueryFullAttributesFile);\nGUEST_FUNCTION_HOOK(__imp__RtlMultiByteToUnicodeN, RtlMultiByteToUnicodeN);\nGUEST_FUNCTION_HOOK(__imp__DbgBreakPoint, DbgBreakPoint);\nGUEST_FUNCTION_HOOK(__imp__MmQueryAllocationSize, MmQueryAllocationSize);\nGUEST_FUNCTION_HOOK(__imp__NtClearEvent, NtClearEvent);\nGUEST_FUNCTION_HOOK(__imp__NtResumeThread, NtResumeThread);\nGUEST_FUNCTION_HOOK(__imp__NtSetEvent, NtSetEvent);\nGUEST_FUNCTION_HOOK(__imp__NtCreateSemaphore, NtCreateSemaphore);\nGUEST_FUNCTION_HOOK(__imp__NtReleaseSemaphore, NtReleaseSemaphore);\nGUEST_FUNCTION_HOOK(__imp__NtWaitForMultipleObjectsEx, NtWaitForMultipleObjectsEx);\nGUEST_FUNCTION_HOOK(__imp__RtlCompareStringN, RtlCompareStringN);\nGUEST_FUNCTION_HOOK(__imp__StfsControlDevice, StfsControlDevice);\nGUEST_FUNCTION_HOOK(__imp__StfsCreateDevice, StfsCreateDevice);\nGUEST_FUNCTION_HOOK(__imp__NtFlushBuffersFile, NtFlushBuffersFile);\nGUEST_FUNCTION_HOOK(__imp__KeQuerySystemTime, KeQuerySystemTime);\nGUEST_FUNCTION_HOOK(__imp__RtlTimeToTimeFields, RtlTimeToTimeFields);\nGUEST_FUNCTION_HOOK(__imp__RtlFreeAnsiString, RtlFreeAnsiString);\nGUEST_FUNCTION_HOOK(__imp__RtlUnicodeStringToAnsiString, RtlUnicodeStringToAnsiString);\nGUEST_FUNCTION_HOOK(__imp__RtlInitUnicodeString, RtlInitUnicodeString);\nGUEST_FUNCTION_HOOK(__imp__ExTerminateThread, ExTerminateThread);\nGUEST_FUNCTION_HOOK(__imp__ExCreateThread, ExCreateThread);\nGUEST_FUNCTION_HOOK(__imp__IoInvalidDeviceRequest, IoInvalidDeviceRequest);\nGUEST_FUNCTION_HOOK(__imp__ObReferenceObject, ObReferenceObject);\nGUEST_FUNCTION_HOOK(__imp__IoCreateDevice, IoCreateDevice);\nGUEST_FUNCTION_HOOK(__imp__IoDeleteDevice, IoDeleteDevice);\nGUEST_FUNCTION_HOOK(__imp__ExAllocatePoolTypeWithTag, ExAllocatePoolTypeWithTag);\nGUEST_FUNCTION_HOOK(__imp__RtlTimeFieldsToTime, RtlTimeFieldsToTime);\nGUEST_FUNCTION_HOOK(__imp__IoCompleteRequest, IoCompleteRequest);\nGUEST_FUNCTION_HOOK(__imp__RtlUpcaseUnicodeChar, RtlUpcaseUnicodeChar);\nGUEST_FUNCTION_HOOK(__imp__ObIsTitleObject, ObIsTitleObject);\nGUEST_FUNCTION_HOOK(__imp__IoCheckShareAccess, IoCheckShareAccess);\nGUEST_FUNCTION_HOOK(__imp__IoSetShareAccess, IoSetShareAccess);\nGUEST_FUNCTION_HOOK(__imp__IoRemoveShareAccess, IoRemoveShareAccess);\nGUEST_FUNCTION_HOOK(__imp__NetDll_XNetStartup, NetDll_XNetStartup);\nGUEST_FUNCTION_HOOK(__imp__NetDll_XNetGetTitleXnAddr, NetDll_XNetGetTitleXnAddr);\nGUEST_FUNCTION_HOOK(__imp__KeWaitForMultipleObjects, KeWaitForMultipleObjects);\nGUEST_FUNCTION_HOOK(__imp__KeRaiseIrqlToDpcLevel, KeRaiseIrqlToDpcLevel);\nGUEST_FUNCTION_HOOK(__imp__KfLowerIrql, KfLowerIrql);\nGUEST_FUNCTION_HOOK(__imp__KeReleaseSemaphore, KeReleaseSemaphore);\nGUEST_FUNCTION_HOOK(__imp__XAudioGetVoiceCategoryVolume, XAudioGetVoiceCategoryVolume);\nGUEST_FUNCTION_HOOK(__imp__XAudioGetVoiceCategoryVolumeChangeMask, XAudioGetVoiceCategoryVolumeChangeMask);\nGUEST_FUNCTION_HOOK(__imp__KeResumeThread, KeResumeThread);\nGUEST_FUNCTION_HOOK(__imp__KeInitializeSemaphore, KeInitializeSemaphore);\nGUEST_FUNCTION_HOOK(__imp__XMAReleaseContext, XMAReleaseContext);\nGUEST_FUNCTION_HOOK(__imp__XMACreateContext, XMACreateContext);\nGUEST_FUNCTION_HOOK(__imp__XAudioRegisterRenderDriverClient, XAudioRegisterRenderDriverClient);\nGUEST_FUNCTION_HOOK(__imp__XAudioUnregisterRenderDriverClient, XAudioUnregisterRenderDriverClient);\nGUEST_FUNCTION_HOOK(__imp__XAudioSubmitRenderDriverFrame, XAudioSubmitRenderDriverFrame);\n"
  },
  {
    "path": "MarathonRecomp/kernel/io/file_system.cpp",
    "content": "#include \"file_system.h\"\n#include <cpu/guest_thread.h>\n#include <cstdio>\n#include <kernel/xam.h>\n#include <kernel/xdm.h>\n#include <kernel/function.h>\n#include <mod/mod_loader.h>\n#include <os/logger.h>\n#include <user/config.h>\n#include <user/paths.h>\n#include <stdafx.h>\n\nstruct FileHandle : KernelObject\n{\n    std::fstream stream;\n    std::filesystem::path path;\n};\n\nstruct FindHandle : KernelObject\n{\n    std::error_code ec;\n    ankerl::unordered_dense::map<std::u8string, std::pair<size_t, bool>> searchResult; // Relative path, file size, is directory\n    decltype(searchResult)::iterator iterator;\n\n    FindHandle(const std::string_view& path)\n    {\n        auto addDirectory = [&](const std::filesystem::path& directory)\n            {\n                for (auto& entry : std::filesystem::directory_iterator(directory, ec))\n                {\n                    std::u8string relativePath = entry.path().lexically_relative(directory).u8string();\n                    searchResult.emplace(relativePath, std::make_pair(entry.is_directory(ec) ? 0 : entry.file_size(ec), entry.is_directory(ec)));\n                }\n            };\n\n        std::string_view pathNoPrefix = path;\n        size_t index = pathNoPrefix.find(\":\\\\\");\n        if (index != std::string_view::npos)\n            pathNoPrefix.remove_prefix(index + 2);\n\n        // Force add a work folder to let the game see the files in mods,\n        // if by some rare chance the user has no DLC or update files.\n        if (pathNoPrefix.empty())\n            searchResult.emplace(u8\"work\", std::make_pair(0, true));\n\n        // Look for only work folder in mod folders, AR files cause issues.\n        if (pathNoPrefix.starts_with(\"work\"))\n        {\n            std::string pathStr(pathNoPrefix);\n            std::replace(pathStr.begin(), pathStr.end(), '\\\\', '/');\n\n            for (size_t i = 0; ; i++)\n            {\n                auto* includeDirs = ModLoader::GetIncludeDirectories(i);\n                if (includeDirs == nullptr)\n                    break;\n\n                for (auto& includeDir : *includeDirs)\n                    addDirectory(includeDir / pathStr);\n            }\n        }\n\n        addDirectory(FileSystem::ResolvePath(path, false));\n\n        iterator = searchResult.begin();\n    }\n\n    void fillFindData(WIN32_FIND_DATAA* lpFindFileData)\n    {\n        if (iterator->second.second)\n            lpFindFileData->dwFileAttributes = ByteSwap(FILE_ATTRIBUTE_DIRECTORY);\n        else\n            lpFindFileData->dwFileAttributes = ByteSwap(FILE_ATTRIBUTE_NORMAL);\n\n        strncpy(lpFindFileData->cFileName, (const char *)(iterator->first.c_str()), sizeof(lpFindFileData->cFileName));\n        lpFindFileData->nFileSizeLow = ByteSwap(uint32_t(iterator->second.first >> 32U));\n        lpFindFileData->nFileSizeHigh = ByteSwap(uint32_t(iterator->second.first));\n        lpFindFileData->ftCreationTime = {};\n        lpFindFileData->ftLastAccessTime = {};\n        lpFindFileData->ftLastWriteTime = {};\n    }\n};\n\nFileHandle* XCreateFileA\n(\n    const char* lpFileName,\n    uint32_t dwDesiredAccess,\n    uint32_t dwShareMode,\n    void* lpSecurityAttributes,\n    uint32_t dwCreationDisposition,\n    uint32_t dwFlagsAndAttributes\n)\n{\n    assert(((dwDesiredAccess & ~(GENERIC_READ | GENERIC_WRITE | FILE_READ_DATA)) == 0) && \"Unknown desired access bits.\");\n    assert(((dwShareMode & ~(FILE_SHARE_READ | FILE_SHARE_WRITE)) == 0) && \"Unknown share mode bits.\");\n    assert(((dwCreationDisposition & ~(CREATE_NEW | CREATE_ALWAYS)) == 0) && \"Unknown creation disposition bits.\");\n\n    std::filesystem::path filePath = FileSystem::ResolvePath(lpFileName, true);\n    std::fstream fileStream;\n    std::ios::openmode fileOpenMode = std::ios::binary;\n    if (dwDesiredAccess & (GENERIC_READ | FILE_READ_DATA))\n    {\n        fileOpenMode |= std::ios::in;\n    }\n\n    if (dwDesiredAccess & GENERIC_WRITE)\n    {\n        fileOpenMode |= std::ios::out;\n    }\n\n    fileStream.open(filePath, fileOpenMode);\n\n    if (!fileStream.is_open()) {\n        std::filesystem::path cachedPath = FindInPathCache(filePath.string());\n        if (!cachedPath.empty()) {\n            fileStream.open(cachedPath, fileOpenMode);\n        }\n    }\n\n    if (!fileStream.is_open())\n    {\n#ifdef _WIN32\n        GuestThread::SetLastError(GetLastError());\n#else\n        switch (errno)\n        {\n        case EACCES:\n            GuestThread::SetLastError(ERROR_ACCESS_DENIED);\n            break;\n        case EEXIST:\n            GuestThread::SetLastError(ERROR_FILE_EXISTS);\n            break;\n        case ENOENT:\n        default: // Use ERROR_PATH_NOT_FOUND as a catch-all for other errors.\n            GuestThread::SetLastError(ERROR_PATH_NOT_FOUND);\n            break;\n        }\n#endif\n        return GetInvalidKernelObject<FileHandle>();\n    }\n\n    FileHandle *fileHandle = CreateKernelObject<FileHandle>();\n    fileHandle->stream = std::move(fileStream);\n    fileHandle->path = std::move(filePath);\n    return fileHandle;\n}\n\nstatic uint32_t XGetFileSizeA(FileHandle* hFile, be<uint32_t>* lpFileSizeHigh)\n{\n    std::error_code ec;\n    auto fileSize = std::filesystem::file_size(hFile->path, ec);\n    if (!ec)\n    {\n        if (lpFileSizeHigh != nullptr)\n        {\n            *lpFileSizeHigh = uint32_t(fileSize >> 32U);\n        }\n    \n        return (uint32_t)(fileSize);\n    }\n\n    return INVALID_FILE_SIZE;\n}\n\nuint32_t XGetFileSizeExA(FileHandle* hFile, LARGE_INTEGER* lpFileSize)\n{\n    std::error_code ec;\n    auto fileSize = std::filesystem::file_size(hFile->path, ec);\n    if (!ec)\n    {\n        if (lpFileSize != nullptr)\n        {\n            lpFileSize->QuadPart = ByteSwap(fileSize);\n        }\n\n        return TRUE;\n    }\n\n    return FALSE;\n}\n\nuint32_t XReadFile\n(\n    FileHandle* hFile,\n    void* lpBuffer,\n    uint32_t nNumberOfBytesToRead,\n    be<uint32_t>* lpNumberOfBytesRead,\n    XOVERLAPPED* lpOverlapped\n)\n{\n    uint32_t result = FALSE;\n    if (lpOverlapped != nullptr)\n    {\n        std::streamoff streamOffset = lpOverlapped->Offset + (std::streamoff(lpOverlapped->OffsetHigh.get()) << 32U);\n        hFile->stream.clear();\n        hFile->stream.seekg(streamOffset, std::ios::beg);\n        if (hFile->stream.bad())\n        {\n            return FALSE;\n        }\n    }\n\n    uint32_t numberOfBytesRead;\n    hFile->stream.read((char *)(lpBuffer), nNumberOfBytesToRead);\n    if (!hFile->stream.bad())\n    {\n        numberOfBytesRead = uint32_t(hFile->stream.gcount());\n        result = TRUE;\n    }\n\n    if (result)\n    {\n        if (lpOverlapped != nullptr)\n        {\n            lpOverlapped->Internal = 0;\n            lpOverlapped->InternalHigh = numberOfBytesRead;\n        }\n        else if (lpNumberOfBytesRead != nullptr)\n        {\n            *lpNumberOfBytesRead = numberOfBytesRead;\n        }\n    }\n\n    return result;\n}\n\nuint32_t XSetFilePointer(FileHandle* hFile, int32_t lDistanceToMove, be<int32_t>* lpDistanceToMoveHigh, uint32_t dwMoveMethod)\n{\n    int32_t distanceToMoveHigh = lpDistanceToMoveHigh ? lpDistanceToMoveHigh->get() : 0;\n    std::streamoff streamOffset = lDistanceToMove + (std::streamoff(distanceToMoveHigh) << 32U);\n    std::fstream::seekdir streamSeekDir = {};\n    switch (dwMoveMethod)\n    {\n    case FILE_BEGIN:\n        streamSeekDir = std::ios::beg;\n        break;\n    case FILE_CURRENT:\n        streamSeekDir = std::ios::cur;\n        break;\n    case FILE_END:\n        streamSeekDir = std::ios::end;\n        break;\n    default:\n        assert(false && \"Unknown move method.\");\n        break;\n    }\n\n    hFile->stream.clear();\n    hFile->stream.seekg(streamOffset, streamSeekDir);\n    if (hFile->stream.bad())\n    {\n        return INVALID_SET_FILE_POINTER;\n    }\n\n    std::streampos streamPos = hFile->stream.tellg();\n    if (lpDistanceToMoveHigh != nullptr)\n        *lpDistanceToMoveHigh = int32_t(streamPos >> 32U);\n\n    return uint32_t(streamPos);\n}\n\nuint32_t XSetFilePointerEx(FileHandle* hFile, int32_t lDistanceToMove, LARGE_INTEGER* lpNewFilePointer, uint32_t dwMoveMethod)\n{\n    std::fstream::seekdir streamSeekDir = {};\n    switch (dwMoveMethod)\n    {\n    case FILE_BEGIN:\n        streamSeekDir = std::ios::beg;\n        break;\n    case FILE_CURRENT:\n        streamSeekDir = std::ios::cur;\n        break;\n    case FILE_END:\n        streamSeekDir = std::ios::end;\n        break;\n    default:\n        assert(false && \"Unknown move method.\");\n        break;\n    }\n\n    hFile->stream.clear();\n    hFile->stream.seekg(lDistanceToMove, streamSeekDir);\n    if (hFile->stream.bad())\n    {\n        return FALSE;\n    }\n\n    if (lpNewFilePointer != nullptr)\n    {\n        lpNewFilePointer->QuadPart = ByteSwap(int64_t(hFile->stream.tellg()));\n    }\n\n    return TRUE;\n}\n\nFindHandle* XFindFirstFileA(const char* lpFileName, WIN32_FIND_DATAA* lpFindFileData)\n{\n    std::string_view path = lpFileName;\n    if (path.find(\"\\\\*\") == (path.size() - 2) || path.find(\"/*\") == (path.size() - 2))\n    {\n        path.remove_suffix(1);\n    }\n    else if (path.find(\"\\\\*.*\") == (path.size() - 4) || path.find(\"/*.*\") == (path.size() - 4))\n    {\n        path.remove_suffix(3);\n    }\n    else\n    {\n        assert(!std::filesystem::path(path).has_extension() && \"Unknown search pattern.\");\n    }\n\n    FindHandle findHandle(path);\n\n    if (findHandle.searchResult.empty())\n        return GetInvalidKernelObject<FindHandle>();\n\n    findHandle.fillFindData(lpFindFileData);\n\n    return CreateKernelObject<FindHandle>(std::move(findHandle));\n}\n\nuint32_t XFindNextFileA(FindHandle* Handle, WIN32_FIND_DATAA* lpFindFileData)\n{\n    Handle->iterator++;\n\n    if (Handle->iterator == Handle->searchResult.end())\n    {\n        return FALSE;\n    }\n    else\n    {\n        Handle->fillFindData(lpFindFileData);\n        return TRUE;\n    }\n}\n\nuint32_t XReadFileEx(FileHandle* hFile, void* lpBuffer, uint32_t nNumberOfBytesToRead, XOVERLAPPED* lpOverlapped, uint32_t lpCompletionRoutine)\n{\n    uint32_t result = FALSE;\n    uint32_t numberOfBytesRead;\n    std::streamoff streamOffset = lpOverlapped->Offset + (std::streamoff(lpOverlapped->OffsetHigh.get()) << 32U);\n    hFile->stream.clear();\n    hFile->stream.seekg(streamOffset, std::ios::beg);\n    if (hFile->stream.bad())\n        return FALSE;\n\n    hFile->stream.read((char *)(lpBuffer), nNumberOfBytesToRead);\n    if (!hFile->stream.bad())\n    {\n        numberOfBytesRead = uint32_t(hFile->stream.gcount());\n        result = TRUE;\n    }\n\n    if (result)\n    {\n        lpOverlapped->Internal = 0;\n        lpOverlapped->InternalHigh = numberOfBytesRead;\n    }\n\n    return result;\n}\n\nuint32_t XGetFileAttributesA(const char* lpFileName)\n{\n    std::filesystem::path filePath = FileSystem::ResolvePath(lpFileName, true);\n    if (std::filesystem::is_directory(filePath))\n        return FILE_ATTRIBUTE_DIRECTORY;\n    else if (std::filesystem::is_regular_file(filePath))\n        return FILE_ATTRIBUTE_NORMAL;\n    else\n        return INVALID_FILE_ATTRIBUTES;\n}\n\nuint32_t XWriteFile(FileHandle* hFile, const void* lpBuffer, uint32_t nNumberOfBytesToWrite, be<uint32_t>* lpNumberOfBytesWritten, void* lpOverlapped)\n{\n    assert(lpOverlapped == nullptr && \"Overlapped not implemented.\");\n\n    hFile->stream.write((const char *)(lpBuffer), nNumberOfBytesToWrite);\n    if (hFile->stream.bad())\n        return FALSE;\n\n    if (lpNumberOfBytesWritten != nullptr)\n        *lpNumberOfBytesWritten = uint32_t(hFile->stream.gcount());\n\n    return TRUE;\n}\n\nstd::filesystem::path FileSystem::ResolvePath(const std::string_view& path, bool checkForMods)\n{\n    LOGF_IMPL(Utility, \"Game\", \"Loading file: \\\"{}\\\"\", path.data());\n    if (checkForMods)\n    {\n        std::filesystem::path resolvedPath = ModLoader::ResolvePath(path);\n\n        if (!resolvedPath.empty())\n        {\n            if (ModLoader::s_isLogTypeConsole)\n                LOGF_IMPL(Utility, \"Mod Loader\", \"Loading file: \\\"{}\\\"\", reinterpret_cast<const char*>(resolvedPath.u8string().c_str()));\n\n            return resolvedPath;\n        }\n    }\n\n    thread_local std::string builtPath;\n    builtPath.clear();\n\n    size_t index = path.find(\":\\\\\");\n    if (index != std::string::npos)\n    {\n        // rooted folder, handle direction\n        const std::string_view root = path.substr(0, index);\n        const auto newRoot = XamGetRootPath(root);\n\n        if (!newRoot.empty())\n        {\n            builtPath += newRoot;\n            builtPath += '/';\n        }\n        \n        builtPath += path.substr(index + 2);\n    }\n    else\n    {\n        builtPath += path;\n    }\n\n    std::replace(builtPath.begin(), builtPath.end(), '\\\\', '/');\n\n    return std::u8string_view((const char8_t*)builtPath.c_str());\n}\n\nGUEST_FUNCTION_HOOK(sub_82537400, XCreateFileA); // replaced\nGUEST_FUNCTION_HOOK(sub_826FD090, XGetFileSizeA); // replaced\nGUEST_FUNCTION_HOOK(sub_826FDC88, XGetFileSizeExA); // replaced\nGUEST_FUNCTION_HOOK(sub_82537118, XReadFile); // replaced\nGUEST_FUNCTION_HOOK(sub_825372B8, XSetFilePointer); // replaced\n// GUEST_FUNCTION_HOOK(sub_831CE888, XSetFilePointerEx);\nGUEST_FUNCTION_HOOK(sub_826F2570, XFindFirstFileA); // replaced\nGUEST_FUNCTION_HOOK(sub_826FD2B8, XFindNextFileA); // replaced\n// GUEST_FUNCTION_HOOK(sub_831CDF40, XReadFileEx);\nGUEST_FUNCTION_HOOK(sub_826FD250, XGetFileAttributesA); // replaced\n// GUEST_FUNCTION_HOOK(sub_831CE3F8, XCreateFileA);\nGUEST_FUNCTION_HOOK(sub_826FCBD0, XWriteFile); // replaced\n"
  },
  {
    "path": "MarathonRecomp/kernel/io/file_system.h",
    "content": "#pragma once\n\nstruct FileSystem\n{\n    static std::filesystem::path ResolvePath(const std::string_view& path, bool checkForMods);\n};\n"
  },
  {
    "path": "MarathonRecomp/kernel/memory.cpp",
    "content": "#include <stdafx.h>\n#include \"memory.h\"\n\nMemory::Memory()\n{\n#ifdef _WIN32\n    base = (uint8_t*)VirtualAlloc((void*)0x100000000ull, PPC_MEMORY_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);\n\n    if (base == nullptr)\n        base = (uint8_t*)VirtualAlloc(nullptr, PPC_MEMORY_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);\n\n    if (base == nullptr)\n        return;\n\n    DWORD oldProtect;\n    VirtualProtect(base, 4096, PAGE_NOACCESS, &oldProtect);\n#else\n    base = (uint8_t*)mmap((void*)0x100000000ull, PPC_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);\n\n    if (base == (uint8_t*)MAP_FAILED)\n        base = (uint8_t*)mmap(NULL, PPC_MEMORY_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);\n\n    if (base == nullptr)\n        return;\n\n    mprotect(base, 4096, PROT_NONE);\n#endif\n\n    for (size_t i = 0; PPCFuncMappings[i].guest != 0; i++)\n    {\n        if (PPCFuncMappings[i].host != nullptr)\n            InsertFunction(PPCFuncMappings[i].guest, PPCFuncMappings[i].host);\n    }\n}\n\nvoid* MmGetHostAddress(uint32_t ptr)\n{\n    return g_memory.Translate(ptr);\n}\n"
  },
  {
    "path": "MarathonRecomp/kernel/memory.h",
    "content": "#pragma once\n\n#ifndef _WIN32\n#define MEM_COMMIT  0x00001000  \n#define MEM_RESERVE 0x00002000  \n#endif\n\nstruct Memory\n{\n    uint8_t* base{};\n\n    Memory();\n\n    bool IsInMemoryRange(const void* host) const noexcept\n    {\n        return host >= base && host < (base + PPC_MEMORY_SIZE);\n    }\n\n    void* Translate(size_t offset) const noexcept\n    {\n        if (offset)\n            assert(offset < PPC_MEMORY_SIZE);\n\n        return base + offset;\n    }\n\n    uint32_t MapVirtual(const void* host) const noexcept\n    {\n        if (host)\n            assert(IsInMemoryRange(host));\n\n        return static_cast<uint32_t>(static_cast<const uint8_t*>(host) - base);\n    }\n\n    PPCFunc* FindFunction(uint32_t guest) const noexcept\n    {\n        return PPC_LOOKUP_FUNC(base, guest);\n    }\n\n    void InsertFunction(uint32_t guest, PPCFunc* host)\n    {\n        PPC_LOOKUP_FUNC(base, guest) = host;\n    }\n};\n\nextern \"C\" void* MmGetHostAddress(uint32_t ptr);\nextern Memory g_memory;\n"
  },
  {
    "path": "MarathonRecomp/kernel/xam.cpp",
    "content": "#include <cstdio>\n#include <fmt/base.h>\n#include <stdafx.h>\n#include \"xam.h\"\n#include \"xdm.h\"\n#include <hid/hid.h>\n#include <ui/game_window.h>\n#include <cpu/guest_thread.h>\n#include <ranges>\n#include <unordered_set>\n#include \"xxHashMap.h\"\n#include <user/paths.h>\n#include <SDL.h>\n\nstruct XamListener : KernelObject\n{\n    uint32_t id{};\n    uint64_t areas{};\n    std::vector<std::tuple<uint32_t, uint32_t>> notifications;\n\n    XamListener(const XamListener&) = delete;\n    XamListener& operator=(const XamListener&) = delete;\n\n    XamListener();\n    ~XamListener();\n};\n\nstruct XamEnumeratorBase : KernelObject\n{\n    virtual uint32_t Next(void* buffer)\n    {\n        return -1;\n    }\n};\n\ntemplate<typename TIterator = std::vector<XHOSTCONTENT_DATA>::iterator>\nstruct XamEnumerator : XamEnumeratorBase\n{\n    uint32_t fetch;\n    size_t size;\n    TIterator position;\n    TIterator begin;\n    TIterator end;\n\n    XamEnumerator() = default;\n    XamEnumerator(uint32_t fetch, size_t size, TIterator begin, TIterator end) : fetch(fetch), size(size), position(begin), begin(begin), end(end)\n    {\n\n    }\n\n    uint32_t Next(void* buffer) override\n    {\n        if (position == end)\n        {\n            return -1;\n        }\n\n        if (buffer == nullptr)\n        {\n            for (size_t i = 0; i < fetch; i++)\n            {\n                if (position == end)\n                {\n                    return i == 0 ? -1 : i;\n                }\n\n                ++position;\n            }\n        }\n\n        for (size_t i = 0; i < fetch; i++)\n        {\n            if (position == end)\n            {\n                return i == 0 ? -1 : i;\n            }\n\n            memcpy(buffer, &*position, size);\n\n            ++position;\n            buffer = (void*)((size_t)buffer + size);\n        }\n\n        return fetch;\n    }\n};\n\nstd::array<xxHashMap<XHOSTCONTENT_DATA>, 3> gContentRegistry{};\nstd::unordered_set<XamListener*> gListeners{};\nxxHashMap<std::string> gRootMap;\n\nstd::string_view XamGetRootPath(const std::string_view& root)\n{\n    const auto result = gRootMap.find(StringHash(root));\n\n    if (result == gRootMap.end())\n        return \"\";\n\n    return result->second;\n}\n\nvoid XamRootCreate(const std::string_view& root, const std::string_view& path)\n{\n    gRootMap.emplace(StringHash(root), path);\n}\n\nXamListener::XamListener()\n{\n    gListeners.insert(this);\n}\n\nXamListener::~XamListener()\n{\n    gListeners.erase(this);\n}\n\nXCONTENT_DATA XamMakeContent(uint32_t type, const std::string_view& name)\n{\n    XCONTENT_DATA data{ 1, type };\n\n    strncpy(data.szFileName, name.data(), sizeof(data.szFileName));\n\n    return data;\n}\n\nvoid XamRegisterContent(const XCONTENT_DATA& data, const std::string_view& root)\n{\n    const auto idx = data.dwContentType - 1;\n\n    gContentRegistry[idx].emplace(StringHash(data.szFileName), XHOSTCONTENT_DATA{ data }).first->second.szRoot = root;\n}\n\nvoid XamRegisterContent(uint32_t type, const std::string_view name, const std::string_view& root)\n{\n    XCONTENT_DATA data{ 1, type, {}, \"\" };\n\n    strncpy(data.szFileName, name.data(), sizeof(data.szFileName));\n\n    XamRegisterContent(data, root);\n}\n\nuint32_t XamNotifyCreateListener(uint64_t qwAreas)\n{\n    auto* listener = CreateKernelObject<XamListener>();\n\n    listener->areas = qwAreas;\n\n    return GetKernelHandle(listener);\n}\n\nvoid XamNotifyEnqueueEvent(uint32_t dwId, uint32_t dwParam)\n{\n    for (const auto& listener : gListeners)\n    {\n        if (((1 << MSG_AREA(dwId)) & listener->areas) == 0)\n            continue;\n\n        listener->notifications.emplace_back(dwId, dwParam);\n    }\n}\n\nbool XNotifyGetNext(uint32_t hNotification, uint32_t dwMsgFilter, be<uint32_t>* pdwId, be<uint32_t>* pParam)\n{\n    auto& listener = *GetKernelObject<XamListener>(hNotification);\n\n    if (dwMsgFilter)\n    {\n        for (size_t i = 0; i < listener.notifications.size(); i++)\n        {\n            if (std::get<0>(listener.notifications[i]) == dwMsgFilter)\n            {\n                if (pdwId)\n                    *pdwId = std::get<0>(listener.notifications[i]);\n\n                if (pParam)\n                    *pParam = std::get<1>(listener.notifications[i]);\n\n                listener.notifications.erase(listener.notifications.begin() + i);\n\n                return true;\n            }\n        }\n    }\n    else\n    {\n        if (listener.notifications.empty())\n            return false;\n\n        if (pdwId)\n            *pdwId = std::get<0>(listener.notifications[0]);\n\n        if (pParam)\n            *pParam = std::get<1>(listener.notifications[0]);\n\n        listener.notifications.erase(listener.notifications.begin());\n\n        return true;\n    }\n\n    return false;\n}\n\nuint32_t XamShowMessageBoxUI(uint32_t dwUserIndex, be<uint16_t>* wszTitle, be<uint16_t>* wszText, uint32_t cButtons,\n    xpointer<be<uint16_t>>* pwszButtons, uint32_t dwFocusButton, uint32_t dwFlags, be<uint32_t>* pResult, XXOVERLAPPED* pOverlapped)\n{\n    *pResult = cButtons ? cButtons - 1 : 0;\n\n#if _DEBUG\n    assert(\"XamShowMessageBoxUI encountered!\" && false);\n#elif _WIN32\n    // This code is Win32-only as it'll most likely crash, misbehave or\n    // cause corruption due to using a different type of memory than what\n    // wchar_t is on Linux. Windows uses 2 bytes while Linux uses 4 bytes.\n    std::vector<std::wstring> texts{};\n\n    texts.emplace_back(reinterpret_cast<wchar_t*>(wszTitle));\n    texts.emplace_back(reinterpret_cast<wchar_t*>(wszText));\n\n    for (size_t i = 0; i < cButtons; i++)\n        texts.emplace_back(reinterpret_cast<wchar_t*>(pwszButtons[i].get()));\n\n    for (auto& text : texts)\n    {\n        for (size_t i = 0; i < text.size(); i++)\n            ByteSwapInplace(text[i]);\n    }\n\n    wprintf(L\"[XamShowMessageBoxUI] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\\n\");\n    wprintf(L\"[XamShowMessageBoxUI] If you are encountering this message and the game has ceased functioning,\\n\");\n    wprintf(L\"[XamShowMessageBoxUI] please create an issue at https://github.com/sonicnext-dev/MarathonRecomp/issues.\\n\");\n    wprintf(L\"[XamShowMessageBoxUI] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\\n\");\n    wprintf(L\"[XamShowMessageBoxUI] %ls\\n\", texts[0].c_str());\n    wprintf(L\"[XamShowMessageBoxUI] %ls\\n\", texts[1].c_str());\n    wprintf(L\"[XamShowMessageBoxUI] \");\n\n    for (size_t i = 0; i < cButtons; i++)\n    {\n        wprintf(L\"%ls\", texts[2 + i].c_str());\n\n        if (i != cButtons - 1)\n            wprintf(L\" | \");\n    }\n\n    wprintf(L\"\\n\");\n    wprintf(L\"[XamShowMessageBoxUI] Defaulted to button: %d\\n\", pResult->get());\n    wprintf(L\"[XamShowMessageBoxUI] !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\\n\");\n#endif\n\n    if (pOverlapped)\n    {\n        pOverlapped->dwCompletionContext = GuestThread::GetCurrentThreadId();\n        pOverlapped->Error = 0;\n        pOverlapped->Length = -1;\n    }\n\n    XamNotifyEnqueueEvent(9, 0);\n\n    return 0;\n}\n\nuint32_t XamContentCreateEnumerator(uint32_t dwUserIndex, uint32_t DeviceID, uint32_t dwContentType,\n    uint32_t dwContentFlags, uint32_t cItem, be<uint32_t>* pcbBuffer, be<uint32_t>* phEnum)\n{\n    if (dwUserIndex != 0)\n    {\n        GuestThread::SetLastError(ERROR_NO_SUCH_USER);\n        return 0xFFFFFFFF;\n    }\n\n    const auto& registry = gContentRegistry[dwContentType - 1];\n    const auto& values = registry | std::views::values;\n    auto* enumerator = CreateKernelObject<XamEnumerator<decltype(values.begin())>>(cItem, sizeof(_XCONTENT_DATA), values.begin(), values.end());\n\n    if (pcbBuffer)\n        *pcbBuffer = sizeof(_XCONTENT_DATA) * cItem;\n\n    *phEnum = GetKernelHandle(enumerator);\n\n    return 0;\n}\n\nuint32_t XamEnumerate(uint32_t hEnum, uint32_t dwFlags, void* pvBuffer, uint32_t cbBuffer, be<uint32_t>* pcItemsReturned, XXOVERLAPPED* pOverlapped)\n{\n    auto* enumerator = GetKernelObject<XamEnumeratorBase>(hEnum);\n    const auto count = enumerator->Next(pvBuffer);\n\n    if (count == -1)\n        return ERROR_NO_MORE_FILES;\n\n    if (pcItemsReturned)\n        *pcItemsReturned = count;\n\n    return ERROR_SUCCESS;\n}\n\n// Patch DLC enumeration format string to allow for whitespace\nPPC_FUNC_IMPL(__imp__sub_825B14B8);\nPPC_FUNC(sub_825B14B8) {\n    const char* _str = (const char*)g_memory.Translate(ctx.r3.u32);\n    uint32_t _1;\n    char _str_end[255];\n    sscanf(_str, \"download:\\\\%08x\\\\%255[^\\n\\r]\", &_1, _str_end);\n\n    XCONTENT_DATA _data;\n    _data.dwContentType = 2;\n    _data.DeviceID = _1;\n\n    memcpy(&_data.szFileName, &_str_end, strlen(_str_end) + 1);\n\n    XamContentCreateEx(0, \"download\", &_data, 3, 0, 0, 0, 0, 0);\n}\n\nuint32_t XamContentCreateEx(uint32_t dwUserIndex, const char* szRootName, const XCONTENT_DATA* pContentData,\n    uint32_t dwContentFlags, be<uint32_t>* pdwDisposition, be<uint32_t>* pdwLicenseMask,\n    uint32_t dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped)\n{\n    const auto& registry = gContentRegistry[pContentData->dwContentType - 1];\n    const auto exists = registry.contains(StringHash(pContentData->szFileName));\n    const auto mode = dwContentFlags & 0xF;\n\n    if (mode == CREATE_ALWAYS)\n    {\n        if (pdwDisposition)\n            *pdwDisposition = XCONTENT_NEW;\n\n        if (!exists)\n        {\n            std::filesystem::path rootPath;\n\n            if (pContentData->dwContentType == XCONTENTTYPE_SAVEDATA)\n            {\n                rootPath = GetSavePath(true);\n            }\n            else if (pContentData->dwContentType == XCONTENTTYPE_DLC)\n            {\n                rootPath = GetGamePath() / \"dlc\";\n            }\n            else\n            {\n                rootPath = GetGamePath();\n            }\n\n            const std::string root = (const char*)rootPath.u8string().c_str();\n            XamRegisterContent(*pContentData, root);\n\n            std::error_code ec;\n            std::filesystem::create_directory(rootPath, ec);\n\n            XamRootCreate(szRootName, root);\n        }\n        else\n        {\n            XamRootCreate(szRootName, registry.find(StringHash(pContentData->szFileName))->second.szRoot);\n        }\n\n        return ERROR_SUCCESS;\n    }\n\n    if (mode == OPEN_EXISTING)\n    {\n        if (exists)\n        {\n            if (pdwDisposition)\n                *pdwDisposition = XCONTENT_EXISTING;\n\n            XamRootCreate(szRootName, registry.find(StringHash(pContentData->szFileName))->second.szRoot);\n\n            return ERROR_SUCCESS;\n        }\n        else\n        {\n            if (pdwDisposition)\n                *pdwDisposition = XCONTENT_NEW;\n\n            return ERROR_PATH_NOT_FOUND;\n        }\n    }\n\n    return ERROR_PATH_NOT_FOUND;\n}\n\nuint32_t XamContentClose(const char* szRootName, XXOVERLAPPED* pOverlapped)\n{\n    gRootMap.erase(StringHash(szRootName));\n    return 0;\n}\n\nuint32_t XamContentGetDeviceData(uint32_t DeviceID, XDEVICE_DATA* pDeviceData)\n{\n    pDeviceData->DeviceID = DeviceID;\n    pDeviceData->DeviceType = XCONTENTDEVICETYPE_HDD;\n    pDeviceData->ulDeviceBytes = 0x10000000;\n    pDeviceData->ulDeviceFreeBytes = 0x10000000;\n    pDeviceData->wszName[0] = 'S';\n    pDeviceData->wszName[1] = 'o';\n    pDeviceData->wszName[2] = 'n';\n    pDeviceData->wszName[3] = 'i';\n    pDeviceData->wszName[4] = 'c';\n    pDeviceData->wszName[5] = '\\0';\n\n    return 0;\n}\n\nuint32_t XamInputGetCapabilities(uint32_t unk, uint32_t userIndex, uint32_t flags, XAMINPUT_CAPABILITIES* caps)\n{\n    if (userIndex != 0)\n        return ERROR_NO_SUCH_USER;\n\n    uint32_t result = hid::GetCapabilities(userIndex, caps);\n\n    if (result == ERROR_SUCCESS)\n    {\n        ByteSwapInplace(caps->Flags);\n        ByteSwapInplace(caps->Gamepad.wButtons);\n        ByteSwapInplace(caps->Gamepad.sThumbLX);\n        ByteSwapInplace(caps->Gamepad.sThumbLY);\n        ByteSwapInplace(caps->Gamepad.sThumbRX);\n        ByteSwapInplace(caps->Gamepad.sThumbRY);\n        ByteSwapInplace(caps->Vibration.wLeftMotorSpeed);\n        ByteSwapInplace(caps->Vibration.wRightMotorSpeed);\n    }\n\n    return result;\n}\n\nuint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_STATE* state)\n{\n    if (userIndex != 0)\n        return ERROR_NO_SUCH_USER;\n\n    memset(state, 0, sizeof(*state));\n\n    if (hid::IsInputAllowed())\n        hid::GetState(userIndex, state);\n\n    auto keyboardState = SDL_GetKeyboardState(NULL);\n\n    if (GameWindow::s_isFocused && !keyboardState[SDL_SCANCODE_LALT])\n    {\n        if (keyboardState[Config::Key_LeftStickUp])\n            state->Gamepad.sThumbLY = 32767;\n        if (keyboardState[Config::Key_LeftStickDown])\n            state->Gamepad.sThumbLY = -32768;\n        if (keyboardState[Config::Key_LeftStickLeft])\n            state->Gamepad.sThumbLX = -32768;\n        if (keyboardState[Config::Key_LeftStickRight])\n            state->Gamepad.sThumbLX = 32767;\n\n        if (keyboardState[Config::Key_RightStickUp])\n            state->Gamepad.sThumbRY = 32767;\n        if (keyboardState[Config::Key_RightStickDown])\n            state->Gamepad.sThumbRY = -32768;\n        if (keyboardState[Config::Key_RightStickLeft])\n            state->Gamepad.sThumbRX = -32768;\n        if (keyboardState[Config::Key_RightStickRight])\n            state->Gamepad.sThumbRX = 32767;\n\n        if (keyboardState[Config::Key_LeftTrigger])\n            state->Gamepad.bLeftTrigger = 0xFF;\n        if (keyboardState[Config::Key_RightTrigger])\n            state->Gamepad.bRightTrigger = 0xFF;\n\n        if (keyboardState[Config::Key_DPadUp])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_UP;\n        if (keyboardState[Config::Key_DPadDown])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_DOWN;\n        if (keyboardState[Config::Key_DPadLeft])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_LEFT;\n        if (keyboardState[Config::Key_DPadRight])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_DPAD_RIGHT;\n\n        if (keyboardState[Config::Key_Start])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_START;\n        if (keyboardState[Config::Key_Back])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_BACK;\n\n        if (keyboardState[Config::Key_LeftBumper])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_LEFT_SHOULDER;\n        if (keyboardState[Config::Key_RightBumper])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_RIGHT_SHOULDER;\n\n        if (keyboardState[Config::Key_A])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_A;\n        if (keyboardState[Config::Key_B])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_B;\n        if (keyboardState[Config::Key_X])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_X;\n        if (keyboardState[Config::Key_Y])\n            state->Gamepad.wButtons |= XAMINPUT_GAMEPAD_Y;\n    }\n\n    state->Gamepad.wButtons &= ~hid::g_prohibitedButtons;\n\n    if (hid::g_isLeftStickProhibited)\n    {\n        state->Gamepad.sThumbLX = 0;\n        state->Gamepad.sThumbLY = 0;\n    }\n\n    if (hid::g_isRightStickProhibited)\n    {\n        state->Gamepad.sThumbRX = 0;\n        state->Gamepad.sThumbRY = 0;\n    }\n\n    ByteSwapInplace(state->Gamepad.wButtons);\n    ByteSwapInplace(state->Gamepad.sThumbLX);\n    ByteSwapInplace(state->Gamepad.sThumbLY);\n    ByteSwapInplace(state->Gamepad.sThumbRX);\n    ByteSwapInplace(state->Gamepad.sThumbRY);\n\n    return ERROR_SUCCESS;\n}\n\nuint32_t XamInputSetState(uint32_t userIndex, uint32_t flags, XAMINPUT_VIBRATION* vibration)\n{\n    if (userIndex != 0)\n        return ERROR_NO_SUCH_USER;\n\n    if (!hid::IsInputDeviceController())\n        return ERROR_SUCCESS;\n\n    ByteSwapInplace(vibration->wLeftMotorSpeed);\n    ByteSwapInplace(vibration->wRightMotorSpeed);\n\n    return hid::SetState(userIndex, vibration);\n}\n"
  },
  {
    "path": "MarathonRecomp/kernel/xam.h",
    "content": "#pragma once\n#include <xbox.h>\n\n#define MSGID(Area, Number) (uint32_t)((uint16_t)(Area) << 16 | (uint16_t)(Number))\n#define MSG_AREA(msgid)     (((msgid) >> 16) & 0xFFFF)\n#define MSG_NUMBER(msgid)   ((msgid) & 0xFFFF)\n\nXCONTENT_DATA XamMakeContent(uint32_t type, const std::string_view& name);\nvoid XamRegisterContent(const XCONTENT_DATA& data, const std::string_view& root);\n\nstd::string_view XamGetRootPath(const std::string_view& root);\nvoid XamRootCreate(const std::string_view& root, const std::string_view& path);\n\nuint32_t XamNotifyCreateListener(uint64_t qwAreas);\nvoid XamNotifyEnqueueEvent(uint32_t dwId, uint32_t dwParam); // i made it the fuck up\nbool XNotifyGetNext(uint32_t hNotification, uint32_t dwMsgFilter, be<uint32_t>* pdwId, be<uint32_t>* pParam);\n\nuint32_t XamShowMessageBoxUI(uint32_t dwUserIndex, be<uint16_t>* wszTitle, be<uint16_t>* wszText, uint32_t cButtons,\n    xpointer<be<uint16_t>>* pwszButtons, uint32_t dwFocusButton, uint32_t dwFlags, be<uint32_t>* pResult, XXOVERLAPPED* pOverlapped);\n\nuint32_t XamContentCreateEnumerator(uint32_t dwUserIndex, uint32_t DeviceID, uint32_t dwContentType,\n    uint32_t dwContentFlags, uint32_t cItem, be<uint32_t>* pcbBuffer, be<uint32_t>* phEnum);\nuint32_t XamEnumerate(uint32_t hEnum, uint32_t dwFlags, void* pvBuffer, uint32_t cbBuffer, be<uint32_t>* pcItemsReturned, XXOVERLAPPED* pOverlapped);\n\nuint32_t XamContentCreateEx(uint32_t dwUserIndex, const char* szRootName, const XCONTENT_DATA* pContentData,\n    uint32_t dwContentFlags, be<uint32_t>* pdwDisposition, be<uint32_t>* pdwLicenseMask,\n    uint32_t dwFileCacheSize, uint64_t uliContentSize, PXXOVERLAPPED pOverlapped);\nuint32_t XamContentGetDeviceData(uint32_t DeviceID, XDEVICE_DATA* pDeviceData);\nuint32_t XamContentClose(const char* szRootName, XXOVERLAPPED* pOverlapped);\n\nuint32_t XamInputGetCapabilities(uint32_t unk, uint32_t userIndex, uint32_t flags, XAMINPUT_CAPABILITIES* caps);\nuint32_t XamInputGetState(uint32_t userIndex, uint32_t flags, XAMINPUT_STATE* state);\nuint32_t XamInputSetState(uint32_t userIndex, uint32_t flags, XAMINPUT_VIBRATION* vibration);\n"
  },
  {
    "path": "MarathonRecomp/kernel/xdbf.h",
    "content": "#pragma once\n\n#include <gpu/video.h>\n#include <xdbf_wrapper.h>\n\nextern XDBFWrapper g_xdbfWrapper;\nextern std::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;\n\nnamespace xdbf\n{\n    inline std::string FixInvalidSequences(std::string& str)\n    {\n        std::string result;\n\n        result.reserve(str.size());\n\n        for (size_t i = 0; i < str.size(); ++i)\n        {\n            auto& c = str[i];\n\n            if (c == '\\r' || c == '\\n')\n            {\n                // Remove spaces before line breaks.\n                while (!result.empty() && result.back() == ' ')\n                    result.pop_back();\n\n                // Skip duplicate line breaks.\n                while (i + 1 < str.size() && (str[i + 1] == '\\r' || str[i + 1] == '\\n'))\n                    ++i;\n\n                // Add a space if the next char isn't one.\n                if (i + 1 < str.size() && str[i + 1] != ' ')\n                    result += ' ';\n            }\n            else\n            {\n                result += c;\n            }\n        }\n\n        return result;\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/kernel/xdm.cpp",
    "content": "#include <stdafx.h>\n#include \"xdm.h\"\n#include \"freelist.h\"\n\nMutex g_kernelLock;\n\nvoid DestroyKernelObject(KernelObject* obj)\n{\n    obj->~KernelObject();\n    g_userHeap.Free(obj);\n}\n\nuint32_t GetKernelHandle(KernelObject* obj)\n{\n    assert(obj != GetInvalidKernelObject());\n    return g_memory.MapVirtual(obj);\n}\n\nvoid DestroyKernelObject(uint32_t handle)\n{\n    DestroyKernelObject(GetKernelObject(handle));\n}\n\nbool IsKernelObject(uint32_t handle)\n{\n    return (handle & 0x80000000) != 0;\n}\n\nbool IsKernelObject(void* obj)\n{\n    return IsKernelObject(g_memory.MapVirtual(obj));\n}\n\nbool IsInvalidKernelObject(void* obj)\n{\n    return obj == GetInvalidKernelObject();\n}\n"
  },
  {
    "path": "MarathonRecomp/kernel/xdm.h",
    "content": "#pragma once\n\n#include \"heap.h\"\n#include \"memory.h\"\n\n#define OBJECT_SIGNATURE           (uint32_t)'XBOX'\n#define GUEST_INVALID_HANDLE_VALUE 0xFFFFFFFF\n\n#ifndef _WIN32\n\n#define S_OK                       0x00000000\n#define FALSE                      0x00000000\n#define TRUE                       0x00000001\n#define STATUS_SUCCESS             0x00000000\n#define STATUS_WAIT_0              0x00000000\n#define STATUS_USER_APC            0x000000C0\n#define STATUS_TIMEOUT             0x00000102\n#define STATUS_NOT_IMPLEMENTED     0xC0000002\n#define STATUS_SEMAPHORE_LIMIT_EXCEEDED             0xC0000047\n#define STATUS_FAIL_CHECK          0xC0000229\n#define INFINITE                   0xFFFFFFFF\n#define FILE_ATTRIBUTE_DIRECTORY   0x00000010  \n#define FILE_ATTRIBUTE_NORMAL      0x00000080  \n#define GENERIC_READ               0x80000000\n#define GENERIC_WRITE              0x40000000\n#define FILE_READ_DATA             0x0001\n#define FILE_SHARE_READ            0x00000001  \n#define FILE_SHARE_WRITE           0x00000002\n#define CREATE_NEW                 1\n#define CREATE_ALWAYS              2\n#define OPEN_EXISTING              3\n#define INVALID_FILE_SIZE          0xFFFFFFFF\n#define INVALID_SET_FILE_POINTER   0xFFFFFFFF\n#define INVALID_FILE_ATTRIBUTES    0xFFFFFFFF\n#define FILE_BEGIN                 0\n#define FILE_CURRENT               1\n#define FILE_END                   2\n#define ERROR_NO_MORE_FILES        0x12\n#define ERROR_NO_SUCH_USER         0x525\n#define ERROR_SUCCESS              0x0\n#define ERROR_PATH_NOT_FOUND       0x3\n#define ERROR_ACCESS_DENIED        0x5\n#define ERROR_FILE_EXISTS          0x50\n#define ERROR_CALL_NOT_IMPLEMENTED 0x78\n#define ERROR_BAD_ARGUMENTS        0xA0\n#define ERROR_TOO_MANY_POSTS       0x12A\n#define ERROR_DEVICE_NOT_CONNECTED 0x48F\n#define PAGE_READWRITE             0x04\n\ntypedef union _LARGE_INTEGER {\n    struct {\n        uint32_t LowPart;\n        int32_t HighPart;\n    };\n    struct {\n        uint32_t LowPart;\n        int32_t HighPart;\n    } u;\n    int64_t QuadPart;\n} LARGE_INTEGER;\n\nstatic_assert(sizeof(LARGE_INTEGER) == 8);\n\ntypedef struct _FILETIME\n{\n    uint32_t dwLowDateTime;\n    uint32_t dwHighDateTime;\n} FILETIME;\n\nstatic_assert(sizeof(FILETIME) == 8);\n\ntypedef struct _WIN32_FIND_DATAA\n{\n    uint32_t dwFileAttributes;\n    FILETIME ftCreationTime;\n    FILETIME ftLastAccessTime;\n    FILETIME ftLastWriteTime;\n    uint32_t nFileSizeHigh;\n    uint32_t nFileSizeLow;\n    uint32_t dwReserved0;\n    uint32_t dwReserved1;\n    char cFileName[260];\n    char cAlternateFileName[14];\n} WIN32_FIND_DATAA;\n\nstatic_assert(sizeof(WIN32_FIND_DATAA) == 320);\n\n#endif\n\nstruct KernelObject\n{\n    virtual ~KernelObject() \n    {\n    }\n\n    virtual uint32_t Wait(uint32_t timeout) \n    {\n        assert(false && \"Wait not implemented for this kernel object.\");\n        return STATUS_TIMEOUT;\n    }\n};\n\ntemplate<typename T, typename... Args>\ninline T* CreateKernelObject(Args&&... args)\n{\n    static_assert(std::is_base_of_v<KernelObject, T>);\n    return g_userHeap.AllocPhysical<T>(std::forward<Args>(args)...);\n}\n\ntemplate<typename T = KernelObject>\ninline T* GetKernelObject(uint32_t handle)\n{\n    assert(handle != GUEST_INVALID_HANDLE_VALUE);\n    return reinterpret_cast<T*>(g_memory.Translate(handle));\n}\n\nuint32_t GetKernelHandle(KernelObject* obj);\n\nvoid DestroyKernelObject(KernelObject* obj);\nvoid DestroyKernelObject(uint32_t handle);\n\nbool IsKernelObject(uint32_t handle);\nbool IsKernelObject(void* obj);\n\nbool IsInvalidKernelObject(void* obj);\n\ntemplate<typename T = void>\ninline T* GetInvalidKernelObject()\n{\n    return reinterpret_cast<T*>(g_memory.Translate(GUEST_INVALID_HANDLE_VALUE));\n}\n\nextern Mutex g_kernelLock;\n\ntemplate<typename T>\ninline T* QueryKernelObject(XDISPATCHER_HEADER& header)\n{\n    std::lock_guard guard{ g_kernelLock };\n    if (header.WaitListHead.Flink != OBJECT_SIGNATURE)\n    {\n        header.WaitListHead.Flink = OBJECT_SIGNATURE;\n        auto* obj = CreateKernelObject<T>(reinterpret_cast<typename T::guest_type*>(&header));\n        header.WaitListHead.Blink = g_memory.MapVirtual(obj);\n\n        return obj;\n    }\n\n    return static_cast<T*>(g_memory.Translate(header.WaitListHead.Blink.get()));\n}\n\n// Get object without initialisation\ntemplate<typename T>\ninline T* TryQueryKernelObject(XDISPATCHER_HEADER& header)\n{\n    if (header.WaitListHead.Flink != OBJECT_SIGNATURE)\n        return nullptr;\n\n    return static_cast<T*>(g_memory.Translate(header.WaitListHead.Blink.get()));\n}"
  },
  {
    "path": "MarathonRecomp/locale/achievement_locale.cpp",
    "content": "#include <user/config.h>\n#include <locale/achievement_locale.h>\n\nstd::unordered_map<int, std::unordered_map<ELanguage, AchievementLocale>> g_achLocale =\n{\n    // Sonic Episode: Cleared\n    {\n        1,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Sonic's Episode: Cleared\",\n                    \"Clear Sonic's episode!\",\n                    \"Cleared Sonic's episode.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ソニックエピソードクリア\",\n                    \"ソニックのエピソードをクリアせよ！\",\n                    \"ソニックのエピソードをクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Sonic's Episode: Abgeschlossen\",\n                    \"Schließe Sonic's Episode ab!\",\n                    \"Sonic's Episode abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Sonic : réussi\",\n                    \"Réussir l'épisode de Sonic!\",\n                    \"Réussissez l'épisode de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Sonic: Superado\",\n                    \"¡Supera el episodio de Sonic!\",\n                    \"Has superado el episodio de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Sonic: completato\",\n                    \"Completa l'episodio di Sonic!\",\n                    \"Hai completato l'episodio di Sonic.\"\n                }\n            },\n        }\n    },\n\n    // Shadow Episode: Cleared\n    {\n        2,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Shadow's Episode: Cleared\",\n                    \"Clear Shadow's episode!\",\n                    \"Cleared Shadow's episode.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シャドウエピソードクリア\",\n                    \"シャドウのエピソードをクリアせよ！\",\n                    \"シャドウのエピソードをクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Shadow's Episode: Abgeschlossen\",\n                    \"Schließe Shadow's Episode ab!\",\n                    \"Shadow's Episode abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Shadow : réussi\",\n                    \"Réussir l'épisode de Shadow!\",\n                    \"Réussissez l'épisode de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Shadow: Superado\",\n                    \"¡Supera el episodio de Shadow!\",\n                    \"Has superado el episodio de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Shadow: completato\",\n                    \"Completa l'episodio di Shadow!\",\n                    \"Hai completato l'episodio di Shadow.\"\n                }\n            },\n        }\n    },\n    \n    // Silver Episode: Cleared\n    {\n        3,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Silver's Episode: Cleared\",\n                    \"Clear Silver's episode!\",\n                    \"Cleared Silver's episode.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シルバーエピソードクリア\",\n                    \"シルバーのエピソードをクリアせよ！\",\n                    \"シルバーのエピソードをクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Silver's Episode: Abgeschlossen\",\n                    \"Schließe Silver's Episode ab!\",\n                    \"Silver's Episode abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Silver : réussi\",\n                    \"Réussir l'épisode de Silver!\",\n                    \"Réussissez l'épisode de Silver.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Silver: Superado\",\n                    \"¡Supera el episodio de Silver!\",\n                    \"Has superado el episodio de Silver.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Silver: completato\",\n                    \"Completa l'episodio di Silver!\",\n                    \"Hai completato l'episodio di Silver.\"\n                }\n            },\n        }\n    },\n    \n    // One to reach the end\n    {\n        4,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"One To Reach The End\",\n                    \"Clear the last hidden episode!\",\n                    \"Cleared the last episode.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"終わりを刻む者\",\n                    \"ラスト隠れたエピソードをクリアせよ！\",\n                    \"ラストエピソードをクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Das Ende erreicht\",\n                    \"Schließe die letzte versteckte Episode ab!\",\n                    \"Die letzte Episode abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"A atteint la fin\",\n                    \"Réussir dans l'épisode secret final!\",\n                    \"Réussissez dans l'épisode final.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Llegar hasta el final\",\n                    \"¡Supera el último episodio secreto!\",\n                    \"Has superado el último episodio.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Colui che giunse alla fine\",\n                    \"Concludi l'ultimo episodio nascosto!\",\n                    \"Hai completato l'ultimo episodio.\"\n                }\n            },\n        }\n    },\n    \n    // Sonic Episode: Completed\n    {\n        5,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Sonic's Episode: Completed\",\n                    \"Clear all of Sonic's hard ACT missions.\",\n                    \"Cleared all of Sonic's ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ソニックエピソードコンプリート\",\n                    \"ソニックのACTミッションを全てクリアせよ\",\n                    \"ソニックのACTミッションを全てクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Sonic's Episode: absolviert\",\n                    \"Absolviere alle von Sonic's AKT-Missionen auf der schwierigsten Stufe.\",\n                    \"Alle von Sonic's AKT-Missionen absolviert.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Sonic : terminé\",\n                    \"Réussir toutes les missions ACTE difficiles de Sonic.\",\n                    \"Réussissez toutes les missions ACTE de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Sonic: Completado\",\n                    \"Supera todas las misiones ACTO de Sonic en modo difícil.\",\n                    \"Has superado todas las misiones ACTO de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Sonic: superato\",\n                    \"Completa tutte le missioni ATTO difficili di Sonic.\",\n                    \"Hai completato tutte le missioni ATTO difficili di Sonic.\"\n                }\n            },\n        }\n    },\n    \n    // Shadow Episode: Completed\n    {\n        6,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Shadow's Episode: Completed\",\n                    \"Clear all of Shadow's hard ACT missions.\",\n                    \"Cleared all of Shadow's ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シャドウエピソードコンプリート\",\n                    \"シャドウのACTミッションを全てクリアせよ\",\n                    \"シャドウのACTミッションを全てクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Shadow's Episode: absolviert\",\n                    \"Absolviere alle von Shadow's AKT-Missionen auf der schwierigsten Stufe.\",\n                    \"Alle von Shadow's AKT-Missionen absolviert.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Shadow : terminé\",\n                    \"Réussir toutes les missions ACTE difficiles de Shadow.\",\n                    \"Réussissez toutes les missions ACTE difficiles de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Shadow: Completado\",\n                    \"Supera todas las misiones ACTO de Shadow en modo difícil.\",\n                    \"Has superado todas las misiones ACTO de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Shadow: superato\",\n                    \"Completa tutte le missioni ATTO difficili di Shadow.\",\n                    \"Hai completato tutte le missioni ATTO difficili di Shadow.\"\n                }\n            },\n        }\n    },\n    \n    // Silver Episode: Completed\n    {\n        7,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Silver's Episode: Completed\",\n                    \"Clear all of Silver's hard ACT missions.\",\n                    \"Cleared all of Silver's ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シルバーエピソードコンプリート\",\n                    \"シルバーのACTミッションを全てクリアせよ\",\n                    \"シルバーのACTミッションを全てクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Silver's Episode: absolviert\",\n                    \"Absolviere alle von Silver's AKT-Missionen auf der schwierigsten Stufe.\",\n                    \"Alle von Silver's AKT-Missionen absolviert.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Silver : terminé\",\n                    \"Réussir toutes les missions ACTE difficiles de Silver.\",\n                    \"Réussissez toutes les missions ACTE difficiles de Silver.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Silver: Completado\",\n                    \"Supera todas las misiones ACTO de Silver en modo difícil.\",\n                    \"Has superado todas las misiones ACTO de Silver.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Silver: superato\",\n                    \"Completa tutte le missioni ATTO difficili di Silver.\",\n                    \"Hai completato tutte le missioni ATTO difficili di Silver.\"\n                }\n            },\n        }\n    },\n    \n    // Shadow Episode: Mastered\n    {\n        8,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Shadow's Episode: Mastered\",\n                    \"Clear all of Shadow's ACT missions with Rank S.\",\n                    \"Achieved Rank S on all of Shadow's ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シャドウエピソードマスター\",\n                    \"シャドウのACTミッションを全てランクSでクリアせよ\",\n                    \"シャドウのACTミッションを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Shadow's Episode: gemeistert\",\n                    \"Schließe alle von Shadow's AKT-Missionen mit einen S-Rang ab.\",\n                    \"Einen S-Rang bei allen AKT-Missionen von Shadow erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Shadow : dominé\",\n                    \"Terminez toutes les missions ACTE de Shadow avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions ACTE de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Shadow: Dominado\",\n                    \"Supera todas las misiones ACTO de Shadow con rango S.\",\n                    \"Has conseguido el rango S en todas las misiones ACTO de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Shadow: stravinto\",\n                    \"Completa tutte le missioni ATTO di Shadow in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni ATTO di Shadow.\"\n                }\n            },\n        }\n    },\n    \n    // Sonic Episode: Mastered\n    {\n        9,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Sonic's Episode: Mastered\",\n                    \"Clear all of Sonic's ACT missions with Rank S.\",\n                    \"Achieved Rank S on all of Sonic's ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ソニックエピソードマスター\",\n                    \"ソニックのACTミッションを全てランクSでクリアせよ\",\n                    \"ソニックのACTミッションを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Sonic's Episode: gemeistert\",\n                    \"Schließe alle von Sonic's AKT-Missionen mit einen S-Rang ab.\",\n                    \"Einen S-Rang bei allen AKT-Missionen von Sonic erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Sonic : dominé\",\n                    \"Terminez toutes les missions ACTE de Sonic avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions ACTE de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Sonic: Dominado\",\n                    \"Supera todas las misiones ACTO de Sonic con rango S.\",\n                    \"Has conseguido el rango S en todas las misiones ACTO de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Sonic: stravinto\",\n                    \"Completa tutte le missioni ATTO di Sonic in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni ATTO di Sonic.\"\n                }\n            },\n        }\n    },\n    \n    // Silver Episode: Mastered\n    {\n        10,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Silver's Episode: Mastered\",\n                    \"Clear all of Silver's ACT missions with Rank S.\",\n                    \"Achieved Rank S on all of Silver's ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シルバーエピソードマスター\",\n                    \"シルバーのACTミッションを全てランクSでクリアせよ\",\n                    \"シルバーのACTミッションを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Silver's Episode: gemeistert\",\n                    \"Schließe alle von Silver's AKT-Missionen mit einen S-Rang ab.\",\n                    \"Einen S-Rang bei allen AKT-Missionen von Silver erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Episode de Silver : dominé\",\n                    \"Terminez toutes les missions ACTE de Silver avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions ACTE de Silver.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Episodio de Silver: Dominado\",\n                    \"Supera todas las misiones ACTO de Silver con rango S.\",\n                    \"Has conseguido el rango S en todas las misiones ACTO de Silver.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Episodio di Silver: stravinto\",\n                    \"Completa tutte le missioni ATTO di Silver in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni ATTO di Silver.\"\n                }\n            },\n        }\n    },\n\n    // Nights of Kronos\n    {\n        11,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Knights of Kronos\",\n                    \"Clear all of the last hidden episode with Rank S.\",\n                    \"Achieved Rank S on all of the last episode.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ナイツオブクロノス\",\n                    \"ラスト隠れたエピソードを全てランクSでクリアせよ\",\n                    \"ラストエピソードを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Ritter von Kronos\",\n                    \"Schließe alles von der letzten versteckten Episode mit einem S-Rang ab.\",\n                    \"Einen S-Rang in der letzten Episode erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Chevaliers de Kronos\",\n                    \"Terminez toutes l'épisode secret final avec un rang S.\",\n                    \"Obtenu un rang S dans toutes l'épisode secret final.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Caballeros de Kronos\",\n                    \"Supera todo el último episodio secreto con rango S.\",\n                    \"Has conseguido el rango S en todo el último episodio.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Cavalieri di Kronos\",\n                    \"Completa tutto l'ultimo episodio nascosto in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutto l'ultimo episodio.\"\n                }\n            },\n        }\n    },\n\n    // Legend of Soleanna\n    {\n        12,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Legend of Soleanna\",\n                    \"Clear all ACT missions with Rank S.\",\n                    \"Achieved Rank S on all ACT missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ソレアナの伝説\",\n                    \"全てのACTミッションをランクSでクリアせよ\",\n                    \"全てのACTミッションをランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Legende von Soleanna\",\n                    \"Schließe alle AKT-Missionen mit einem S-Rang ab.\",\n                    \"Einen S-Rang bei allen AKT-Missionen erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Légende de Soleanna\",\n                    \"Terminez toutes les missions ACTE avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions ACTE.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Leyenda de Soleanna\",\n                    \"Supera todas las misiones ACTO con rango S.\",\n                    \"Has conseguido el rango S en todas las misiones ACTO.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Leggenda di Soleanna\",\n                    \"Completa tutte le missioni ATTO in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni ATTO.\"\n                }\n            },\n        }\n    },\n\n    // Silver Medalist\n    {\n        13,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Silver Medalist\",\n                    \"Collect all of the Silver Medals scattered around Soleanna.\",\n                    \"Collected all of the Silver Medals in Soleanna.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"シルバーメダリスト\",\n                    \"ソレアナのシルバーメダルを全て取得せよ\",\n                    \"ソレアナのシルバーメダルを全て取得した\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Silbermedaillengewinner\",\n                    \"Sammel alle Silbermedaillen die um Soleanna verstreut sind.\",\n                    \"Alle Silbermedaillen in Soleanna gesammelt.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Médaillé d'argent\",\n                    \"Collectionnez toutes les médailles d'argent dispersées autour de Soleanna.\",\n                    \"Collectionné toutes les médailles d'argent à Soleanna.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Medallista de plata\",\n                    \"Reúne todas las medallas de plata repartidas por Soleanna.\",\n                    \"Has conseguido todas las medallas de plata de Soleanna.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Medaglie d'argento\",\n                    \"Raccogli tutte le medaglie d'argento disseminate in giro per Soleanna.\",\n                    \"Hai raccolto tutte le medaglie d'argento di Soleanna.\"\n                }\n            },\n        }\n    },\n\n    // Gold Medalist\n    {\n        14,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Gold Medalist\",\n                    \"Collect all of the legendary Gold Medals of Soleanna.\",\n                    \"Collected all of the Gold Medals of Soleanna.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ゴールドメダリスト\",\n                    \"ソレアナの伝説的なゴールドメダルを全て取得せよ\",\n                    \"ソレアナのゴールドメダルを全て取得した\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Goldmedaillengewinner\",\n                    \"Sammel alle legendäre Goldmedaillen in Soleanna.\",\n                    \"Alle Goldmedaillen in Soleanna gesammelt.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Médaillé d'or\",\n                    \"Collectionnez toutes les médailles d'or légendaires de Soleanna.\",\n                    \"Collectionné toutes les médailles d'or légendaires de Soleanna.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Medallista de oro\",\n                    \"Reúne todas las legendarias medallas de oro de Soleanna.\",\n                    \"Has conseguido todas las medallas de oro de Soleanna.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Medaglie d'oro\",\n                    \"Raccogli tutte le medaglie d'oro di Soleanna.\",\n                    \"Hai raccolto tutte le medaglie d'oro di Soleanna.\"\n                }\n            },\n        }\n    },\n\n    // Blue Phantom\n    {\n        15,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Blue Phantom\",\n                    \"Unlock all of Sonic's abilities.\",\n                    \"Learned all of Sonic's abilities.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"蒼き幻影\",\n                    \"ソニックのアクションを全て習得せよ\",\n                    \"ソニックのアクションを全て習得した\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Blaues Phantom\",\n                    \"Schalte alle von Sonic's Fähigkeiten frei.\",\n                    \"Alle von Sonic's Fähigkeiten gelernt.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Fantôme bleu\",\n                    \"Débloquez toutes les capacités de Sonic.\",\n                    \"Appris toutes les capacités de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Fantasma azul\",\n                    \"Desbloquea todas las habilidades de Sonic.\",\n                    \"Has aprendido todas las habilidades de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Fantasma Blu\",\n                    \"Sblocca tutte le abilità di Sonic.\",\n                    \"Hai imparato tutte le abilità di Sonic.\"\n                }\n            },\n        }\n    },\n\n    // Ultimate Life Form\n    {\n        16,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Ultimate Life Form\",\n                    \"Unlock all of Shadow's abilities.\",\n                    \"Learned all of Shadow's abilities.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"究極の生命体\",\n                    \"シャドウのアクションを全て習得せよ\",\n                    \"シャドウのアクションを全て習得した\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Ultimative Lebensform\",\n                    \"Schalte alle von Shadow's Fähigkeiten frei.\",\n                    \"Alle von Shadow's Fähigkeiten gelernt.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Forme de vie ultime\",\n                    \"Débloquez toutes les capacités de Shadow.\",\n                    \"Appris toutes les capacités de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Forma de vida definitiva\",\n                    \"Desbloquea todas las habilidades de Shadow.\",\n                    \"Has aprendido todas las habilidades de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Essere Supremo\",\n                    \"Sblocca tutte le abilità di Shadow.\",\n                    \"Hai imparato tutte le abilità di Shadow.\"\n                }\n            },\n        }\n    },\n\n    // Psychic Soldier\n    {\n        17,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Psychic Soldier\",\n                    \"Unlock all of Silver's abilities.\",\n                    \"Learned all of Silver's abilities.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"サイキックソルジャー\",\n                    \"シルバーのアクションを全て習得せよ\",\n                    \"シルバーのアクションを全て習得した\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Übernatürlicher Soldat\",\n                    \"Schalte alle von Silver's Fähigkeiten frei.\",\n                    \"Alle von Silver's Fähigkeiten gelernt.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Soldat psychique\",\n                    \"Débloquez toutes les capacités de Silver.\",\n                    \"Appris toutes les capacités de Silver.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Soldado psíquico\",\n                    \"Desbloquea todas las habilidades de Silver.\",\n                    \"Has aprendido todas las habilidades de Silver.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Soldato Psichico\",\n                    \"Sblocca tutte le abilità di Silver.\",\n                    \"Hai imparato tutte le abilità di Silver.\"\n                }\n            },\n        }\n    },\n\n    // Soleanna's Hero\n    {\n        18,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Soleanna's Hero\",\n                    \"Clear all of Sonic's TOWN missions.\",\n                    \"Cleared all of Sonic's TOWN missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ソレアナの英雄\",\n                    \"ソニックのタウンミッションを全てクリアせよ\",\n                    \"ソニックのタウンミッションを全てクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Soleanna's Held\",\n                    \"Schließe alle von Sonic's STADT-Missionen ab.\",\n                    \"Alle von Sonic's STADT-Missionen abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Héros de Soleanna\",\n                    \"Terminez toutes les missions VILLE de Sonic.\",\n                    \"Terminé toutes les missions VILLE de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Héroe de Soleanna\",\n                    \"Supera todas las misiones CIUDAD de Sonic.\",\n                    \"Has superado todas las misiones CIUDAD de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Eroe di Soleanna\",\n                    \"Completa tutte le missioni CITTÀ di Sonic.\",\n                    \"Hai completato tutte le missioni CITTÀ di Sonic.\"\n                }\n            },\n        }\n    },\n\n    // Elite Agent\n    {\n        19,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Elite Agent\",\n                    \"Clear all of Shadow's TOWN missions.\",\n                    \"Cleared all of Shadow's TOWN missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"エリートエージェント\",\n                    \"シャドウのタウンミッションを全てクリアせよ\",\n                    \"シャドウのタウンミッションを全てクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Elite-Agent\",\n                    \"Schließe alle von Shadow's STADT-Missionen ab.\",\n                    \"Alle von Shadow's STADT-Missionen abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Agent d'élite\",\n                    \"Terminez toutes les missions VILLE de Shadow.\",\n                    \"Terminé toutes les missions VILLE de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Agente de élite\",\n                    \"Supera todas las misiones CIUDAD de Shadow.\",\n                    \"Has superado todas las misiones CIUDAD de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Agente Scelto\",\n                    \"Completa tutte le missioni CITTÀ di Shadow.\",\n                    \"Hai completato tutte le missioni CITTÀ di Shadow.\"\n                }\n            },\n        }\n    },\n\n    // Silver The Liberator\n    {\n        20,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Silver the Liberator\",\n                    \"Clear all of Silver's TOWN missions.\",\n                    \"Cleared all of Silver's TOWN missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"解放者シルバー\",\n                    \"シルバーのタウンミッションを全てクリアせよ\",\n                    \"シルバーのタウンミッションを全てクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Silver der Befreier\",\n                    \"Schließe alle von Silver's STADT-Missionen ab.\",\n                    \"Alle von Silver's STADT-Missionen abgeschlossen.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Silver le libérateur\",\n                    \"Terminez toutes les missions VILLE de Silver.\",\n                    \"Terminé toutes les missions VILLE de Silver.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Silver el Liberador\",\n                    \"Supera todas las misiones CIUDAD de Silver.\",\n                    \"Has superado todas las misiones CIUDAD de Silver.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Silver il Liberatore\",\n                    \"Completa tutte le missioni CITTÀ di Silver.\",\n                    \"Hai completato tutte le missioni CITTÀ di Silver.\"\n                }\n            },\n        }\n    },\n\n    // Soleanna's blue wind\n    {\n        21,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Soleanna's Blue Wind\",\n                    \"Clear all of Sonic's TOWN missions with Rank S.\",\n                    \"Achieved Rank S on all of Sonic's TOWN missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ソレアナの蒼き風\",\n                    \"ソニックのタウンミッションを全てランクSでクリアせよ\",\n                    \"ソニックのタウンミッションを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Soleanna's Blauer Wind\",\n                    \"Schließe alle von Sonic's STADT-Missionen mit einem S-Rang ab.\",\n                    \"Einen S-Rang in allen STADT-Missionen mit Sonic erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Vent bleu de Soleanna\",\n                    \"Terminez toutes les missions VILLE de Sonic avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions VILLE de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"El viento azul de Soleanna\",\n                    \"Supera todas las misiones CIUDAD de Sonic con rango S.\",\n                    \"Has conseguido rango S en todas las misiones CIUDAD de Sonic.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Vento Blu di Soleanna\",\n                    \"Completa tutte le missioni CITTÀ di Sonic in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni CITTÀ di Sonic.\"\n                }\n            },\n        }\n    },\n\n    // Dark Hero\n    {\n        22,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Dark Hero\",\n                    \"Clear all of Shadow's TOWN missions with Rank S.\",\n                    \"Achieved Rank S on all of Shadow's TOWN missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"ダーク英雄\",\n                    \"シャドウのタウンミッションを全てランクSでクリアせよ\",\n                    \"シャドウのタウンミッションを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Dunkler Held\",\n                    \"Schließe alle von Shadow's STADT-Missionen mit einem S-Rang ab.\",\n                    \"Einen S-Rang in allen STADT-Missionen mit Shadow erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Héros des ténèbres\",\n                    \"Terminez toutes les missions VILLE de Shadow avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions VILLE de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Héroe oscuro\",\n                    \"Supera todas las misiones CIUDAD de Shadow con rango S.\",\n                    \"Has conseguido rango S en todas las misiones CIUDAD de Shadow.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Eroe Oscuro\",\n                    \"Completa tutte le missioni CITTÀ di Shadow in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni CITTÀ di Shadow.\"\n                }\n            },\n        }\n    },\n\n    // Silver The Savior.\n    {\n        23,\n        {\n            {\n                ELanguage::English,\n                {\n                    \"Silver the Savior\",\n                    \"Clear all of Silver's TOWN missions with Rank S.\",\n                    \"Achieved Rank S on all of Silver's TOWN missions.\"\n                }\n            },\n            {\n                ELanguage::Japanese,\n                {\n                    \"救世主シルバー\",\n                    \"シルバーのタウンミッションを全てランクSでクリアせよ\",\n                    \"シルバーのタウンミッションを全てランクSでクリアした\"\n                }\n            },\n            {\n                ELanguage::German,\n                {\n                    \"Silver der Retter\",\n                    \"Schließe alle von Silver's STADT-Missionen mit einem S-Rang ab.\",\n                    \"Einen S-Rang in allen STADT-Missionen mit Silver erreicht.\"\n                }\n            },\n            {\n                ELanguage::French,\n                {\n                    \"Silver le sauveur\",\n                    \"Terminez toutes les missions VILLE de Silver avec un rang S.\",\n                    \"Obtenu un rang S dans toutes les missions VILLE de Silver.\"\n                }\n            },\n            {\n                ELanguage::Spanish,\n                {\n                    \"Silver el Salvador\",\n                    \"Supera todas las misiones CIUDAD de Silver con rango S.\",\n                    \"Has conseguido rango S en todas las misiones CIUDAD de Silver.\"\n                }\n            },\n            {\n                ELanguage::Italian,\n                {\n                    \"Silver il Salvatore\",\n                    \"Completa tutte le missioni CITTÀ di Silver in posizione S.\",\n                    \"Hai ottenuto la posizione S in tutte le missioni CITTÀ di Silver.\"\n                }\n            },\n        }\n    }\n};\n\nAchievementLocale& GetAchievementLocale(const int key)\n{\n    auto localeFindResult = g_achLocale.find(key);\n\n    if (localeFindResult != g_achLocale.end())\n    {\n        auto languageFindResult = localeFindResult->second.find(Config::Language);\n\n        if (languageFindResult == localeFindResult->second.end())\n            languageFindResult = localeFindResult->second.find(ELanguage::English);\n\n        if (languageFindResult != localeFindResult->second.end())\n            return languageFindResult->second;\n    }\n\n    return g_achLocaleMissing;\n}\n"
  },
  {
    "path": "MarathonRecomp/locale/achievement_locale.h",
    "content": "#pragma once\n\n#include <locale/locale.h>\n\nstruct AchievementLocale\n{\n    std::string Name{};\n    std::string LockedDesc{};\n    std::string UnlockedDesc{};\n};\n\ninline AchievementLocale g_achLocaleMissing =\n{\n    g_localeMissing,\n    g_localeMissing,\n    g_localeMissing\n};\n\nextern std::unordered_map<int, std::unordered_map<ELanguage, AchievementLocale>> g_achLocale;\n\nAchievementLocale& GetAchievementLocale(const int key);\n"
  },
  {
    "path": "MarathonRecomp/locale/config_locale.cpp",
    "content": "#include <user/config.h>\n\n/*\n    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! LOCALISATION NOTES !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n    - Ensure brand names are always presented on the same line.\n\n      Correct:\n      This is a string that contains a brand name like\n      Xbox 360, which is one of the two consoles to have a port of\n      SONIC THE HEDGEHOG.\n\n      Incorrect:\n      This is a string that contains a brand name like Xbox\n      360, which is one of the two consoles to have a port of SONIC THE\n      HEDGEHOG.\n\n    - Ensure your locale is added in the correct order following the language enum.\n\n      Correct:\n      {\n          { ELanguage::English,  \"Example\" },\n          { ELanguage::Japanese, \"Example\" },\n          { ELanguage::German,   \"Example\" },\n          { ELanguage::French,   \"Example\" },\n          { ELanguage::Spanish,  \"Example\" },\n          { ELanguage::Italian,  \"Example\" }\n      }\n\n      Incorrect:\n      {\n          { ELanguage::English,  \"Example\" },\n          { ELanguage::French,   \"Example\" },\n          { ELanguage::Spanish,  \"Example\" },\n          { ELanguage::German,   \"Example\" },\n          { ELanguage::Italian,  \"Example\" },\n          { ELanguage::Japanese, \"Example\" }\n      }\n\n    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n*/\n\n#define CONFIG_DEFINE_LOCALE(name) \\\n    CONFIG_LOCALE g_##name##_locale =\n\n#define CONFIG_DEFINE_ENUM_LOCALE(type) \\\n    CONFIG_ENUM_LOCALE(type) g_##type##_locale =\n\nCONFIG_DEFINE_LOCALE(Language)\n{\n    { ELanguage::English,  { \"Language\", \"Change the language used for text.\" } },\n    { ELanguage::Japanese, { \"言語\", \"ゲーム内の表示言語を変更できます\" } },\n    { ELanguage::German,   { \"Sprache\", \"Ändere die Textsprache.\" } },\n    { ELanguage::French,   { \"Langue\", \"Modifier la langue utilisée pour le texte.\" } },\n    { ELanguage::Spanish,  { \"Idioma\", \"Cambia el idioma utilizado para los textos.\" } },\n    { ELanguage::Italian,  { \"Lingua\", \"Cambia la lingua utilizzata per il testo.\" } }\n};\n\n// Notes: do not localise this.\nCONFIG_DEFINE_ENUM_LOCALE(ELanguage)\n{\n    {\n        ELanguage::English,\n        {\n            { ELanguage::English,  { \"English\", \"\" } },\n            { ELanguage::Japanese, { \"日本語\", \"\" } },\n            { ELanguage::German,   { \"Deutsch\", \"\" } },\n            { ELanguage::French,   { \"Français\", \"\" } },\n            { ELanguage::Spanish,  { \"Español\", \"\" } },\n            { ELanguage::Italian,  { \"Italiano\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(VoiceLanguage)\n{\n    { ELanguage::English,  { \"Voice Language\", \"Change the language used for character voices.\" } },\n    { ELanguage::Japanese, { \"音声言語\", \"ゲーム内の音声言語を変更できます\" } },\n    { ELanguage::German,   { \"Stimmeinstellung\", \"Ändere die Sprache, die für Charakterstimmen benutzt wird.\" } },\n    { ELanguage::French,   { \"Langue de doublage\", \"Modifie la langue utilisée pour la voix des personnages.\" } },\n    { ELanguage::Spanish,  { \"Idioma de voz\", \"Cambia el idioma utilizado para las voces de los personajes.\" } },\n    { ELanguage::Italian,  { \"Lingua delle voci\", \"Modifica la lingua utilizzata per le voci dei personaggi.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EVoiceLanguage)\n{\n    {\n        ELanguage::English,\n        {\n            { EVoiceLanguage::English,  { \"English\", \"\" } },\n            { EVoiceLanguage::Japanese, { \"Japanese\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { EVoiceLanguage::English,  { \"英語\", \"\" } },\n            { EVoiceLanguage::Japanese, { \"日本語\", \"\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { EVoiceLanguage::English,  { \"Englisch\", \"\" } },\n            { EVoiceLanguage::Japanese, { \"Japanisch\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { EVoiceLanguage::English,  { \"Anglais\", \"\" } },\n            { EVoiceLanguage::Japanese, { \"Japonais\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { EVoiceLanguage::English,  { \"Inglés\", \"\" } },\n            { EVoiceLanguage::Japanese, { \"Japonés\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { EVoiceLanguage::English,  { \"Inglese\", \"\" } },\n            { EVoiceLanguage::Japanese, { \"Giapponese\", \"\" } }\n        }\n    },\n};\n\nCONFIG_DEFINE_LOCALE(Subtitles)\n{\n    { ELanguage::English,  { \"Subtitles\", \"Show subtitles during dialogue.\" } },\n    { ELanguage::Japanese, { \"字幕\", \"字幕の表示を選択できます\" } },\n    { ELanguage::German,   { \"Untertitel\", \"Zeige Untertitel bei Dialogen.\" } },\n    { ELanguage::French,   { \"Sous-titres\", \"Affiche les sous-titres pendant les dialogues.\" } },\n    { ELanguage::Spanish,  { \"Subtítulos\", \"Muestra subtítulos durante los diálogos.\" } },\n    { ELanguage::Italian,  { \"Sottotitoli\", \"Mostra i sottotitoli durante i dialoghi.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(Hints)\n{\n    { ELanguage::English,  { \"Hints\", \"Show hints during gameplay.\" } },\n    { ELanguage::Japanese, { \"ヒントリング\", \"ゲーム内にヒントリングを表示するか選択できます\" } },\n    { ELanguage::German,   { \"Hinweise\", \"Zeige Hinweise während des Spiels.\" } },\n    { ELanguage::French,   { \"Indices\", \"Affiche les indices pendant le jeu.\" } },\n    { ELanguage::Spanish,  { \"Pistas\", \"Muestra pistas durante el juego.\" } },\n    { ELanguage::Italian,  { \"Indizi\", \"Mostra degli indizzi durante il gioco.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(ControlTutorial)\n{\n    { ELanguage::English,  { \"Control Tutorial\", \"Show controller hints during gameplay.\" } },\n    { ELanguage::Japanese, { \"アクションナビ\", \"ゲーム内にアクションナビを表示するか選択できます\" } },\n    { ELanguage::German,   { \"Steuerungsanleitung\", \"Zeige Steuerungshinweise während des Spiels.\" } },\n    { ELanguage::French,   { \"Indication des commandes\", \"Affiche les indications des commandes pendant le jeu.\" } },\n    { ELanguage::Spanish,  { \"Tutorial de controles\", \"Muestra pistas de controles durante el juego.\" } },\n    { ELanguage::Italian,  { \"Tutorial dei comandi\", \"Mostra i tutorial dei comandi durante il gioco.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(Autosave)\n{\n    { ELanguage::English,  { \"Autosave\", \"Save the game automatically at manual save points.\" } },\n    { ELanguage::Japanese, { \"オートセーブ\", \"手動セーブポイントでゲームを自動的にセーブします\" } },\n    { ELanguage::German,   { \"Automatisches Speichern\", \"Speichert das Spiel automatisch an manuellen Speicherpunkten.\" } },\n    { ELanguage::French,   { \"Sauvegarde Auto\", \"Sauvegarder automatiquement la partie lorsque le jeu propose une sauvegarde.\" } },\n    { ELanguage::Spanish,  { \"Autoguardado\", \"Guarda el juego automáticamente en los puntos de guardado manuales.\" } },\n    { ELanguage::Italian,  { \"Salvataggio automatico\", \"Salva automaticamente il gioco nei punti di salvataggio manuali.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(AchievementNotifications)\n{\n    { ELanguage::English,  { \"Achievement Notifications\", \"Show notifications for unlocking achievements. Achievements will still be rewarded with notifications disabled.\" } },\n    { ELanguage::Japanese, { \"実績通知\", \"実績通知の有無を選択できます オフにしても実績は付与されます\" } },\n    { ELanguage::German,   { \"Erfolgsbenachrichtigungen\", \"Zeige Benachrichtigungen für das Freischalten von Erfolgen. Erfolge werden weiterhin freigeschaltet, auch wenn die Benachrichtigungen ausgeschaltet sind.\" } },\n    { ELanguage::French,   { \"Notification des succès\", \"Affiche les notifications pour le déverrouillage des succès. Les succès seront toujours obtenus même si les notifications sont désactivées.\" } },\n    { ELanguage::Spanish,  { \"Notificaciones de logros\", \"Muestra notificaciones al desbloquear logros. Los logros se seguirán obteniendo aunque las notificaciones estén desactivadas.\" } },\n    { ELanguage::Italian,  { \"Notifiche obiettivi\", \"Mostra delle notifiche quando sblocchi degli obiettivi. Gli obiettivi verranno comunque assegnati anche con le notifiche disattivate.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(HorizontalCamera)\n{\n    { ELanguage::English,  { \"Horizontal Camera\", \"Change how the camera moves left and right.\" } },\n    { ELanguage::Japanese, { \"カメラの左右\", \"カメラ左右の動く方向を選択できます\" } },\n    { ELanguage::German,   { \"Horizontale Kamera\", \"Ändere wie sich die Kamera nach links und rechts bewegt.\" } },\n    { ELanguage::French,   { \"Caméra horizontale\", \"Modifie la rotation horizontale de la caméra.\" } },\n    { ELanguage::Spanish,  { \"Cámara horizontal\", \"Cambia cómo se mueve la camara hacia la izquierda y la derecha.\" } },\n    { ELanguage::Italian,  { \"Telecamera orizzontale\", \"Modifica come la telecamera si muove da sinistra a destra.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(VerticalCamera)\n{\n    { ELanguage::English,  { \"Vertical Camera\", \"Change how the camera moves up and down.\" } },\n    { ELanguage::Japanese, { \"カメラの上下\", \"カメラ上下の動く方向を選択できます\" } },\n    { ELanguage::German,   { \"Vertikale Kamera\", \"Ändere wie sich die Kamera nach oben und unten bewegt.\" } },\n    { ELanguage::French,   { \"Caméra verticale\", \"Modifie la rotation verticale de la caméra.\" } },\n    { ELanguage::Spanish,  { \"Cámara vertical\", \"Cambia cómo se mueve la camara hacia arriba y abajo.\" } },\n    { ELanguage::Italian,  { \"Telecamera verticale\", \"Modifica come la telecamera si muove su e giù.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(ECameraRotationMode)\n{\n    {\n        ELanguage::English,\n        {\n            { ECameraRotationMode::Normal,  { \"Normal\", \"\" } },\n            { ECameraRotationMode::Reverse, { \"Reverse\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { ECameraRotationMode::Normal,  { \"ノーマル\", \"\" } },\n            { ECameraRotationMode::Reverse, { \"リバース\", \"\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { ECameraRotationMode::Normal,  { \"Normal\", \"\" } },\n            { ECameraRotationMode::Reverse, { \"Invertiert\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { ECameraRotationMode::Normal,  { \"Normale\", \"\" } },\n            { ECameraRotationMode::Reverse, { \"Inversée\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { ECameraRotationMode::Normal,  { \"Normal\", \"\" } },\n            { ECameraRotationMode::Reverse, { \"Invertido\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { ECameraRotationMode::Normal,  { \"Normale\", \"\" } },\n            { ECameraRotationMode::Reverse, { \"Invertita\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(AllowBackgroundInput)\n{\n    { ELanguage::English,  { \"Allow Background Input\", \"Allow controller input whilst the game window is unfocused.\" } },\n    { ELanguage::Japanese, { \"バックグラウンド入力\", \"フォーカスされていないゲームに入力できるか選択できます\" } },\n    { ELanguage::German,   { \"Erlaube Hintergrundeingaben\", \"Erlaube Eingaben deines Controllers auch wenn das Spielfenster nicht fokussiert ist.\" } },\n    { ELanguage::French,   { \"Manette en arrière plan\", \"Permet d'utiliser la manette dans le jeu lorsque qu'il n'est pas au premier plan.\" } },\n    { ELanguage::Spanish,  { \"Control en segundo plano\", \"Permite controlar el juego con un mando mientras la ventana esté en segundo plano.\" } },\n    { ELanguage::Italian,  { \"Input con la finestra inattiva\", \"Attiva/disattiva i tasti del controller mentre la finestra è inattiva.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(ControllerIcons)\n{\n    { ELanguage::English,  { \"Controller Icons\", \"Change the icons to match your controller.\" } },\n    { ELanguage::Japanese, { \"コントローラーアイコン\", \"ゲーム内のコントローラーアイコンを変更できます\" } },\n    { ELanguage::German,   { \"Controllersymbole\", \"Ändere die Controllersymbole, um sie auf dein Modell anzupassen.\" } },\n    { ELanguage::French,   { \"Icône des boutons\", \"Modifie les icônes pour les faire correspondre à votre manette.\" } },\n    { ELanguage::Spanish,  { \"Iconos del mando\", \"Cambia los iconos para que coincidan con tu mando.\" } },\n    { ELanguage::Italian,  { \"Icone dei tasti\", \"Modifica le icone per farle corrispondere con il tuo controller.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EControllerIcons)\n{\n    {\n        ELanguage::English,\n        {\n            { EControllerIcons::Auto,        { \"Auto\", \"Auto: the game will determine which icons to use based on the current input device.\" } },\n            { EControllerIcons::Xbox,        { \"Xbox\", \"\" } },\n            { EControllerIcons::PlayStation, { \"PlayStation\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { EControllerIcons::Auto,        { \"自動検出\", \"自動検出: コントローラーアイコンを使用している入力デバイスに応じて自動的に決定します\" } },\n            { EControllerIcons::Xbox,        { \"Xbox\", \"\" } },\n            { EControllerIcons::PlayStation, { \"PlayStation\", \"\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { EControllerIcons::Auto,        { \"Auto\", \"Auto: Das Spiel erkennt automatisch deinen Controller um die Symbole dementsprechend anzupassen.\" } },\n            { EControllerIcons::Xbox,        { \"Xbox\", \"\" } },\n            { EControllerIcons::PlayStation, { \"PlayStation\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { EControllerIcons::Auto,        { \"Auto\", \"Auto : le jeu déterminera automatiquement quelles icônes utiliser en fonction du périphérique d'entrée.\" } },\n            { EControllerIcons::Xbox,        { \"Xbox\", \"\" } },\n            { EControllerIcons::PlayStation, { \"PlayStation\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { EControllerIcons::Auto,        { \"Auto\", \"Auto: el juego determinará de forma automática qué iconos utilizar dependiendo del dispositivo de entrada actual.\" } },\n            { EControllerIcons::Xbox,        { \"Xbox\", \"\" } },\n            { EControllerIcons::PlayStation, { \"PlayStation\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { EControllerIcons::Auto,        { \"Auto\", \"Auto: il gioco determinerà quali icone da utilizzare in base al dispositivo di input attuale.\" } },\n            { EControllerIcons::Xbox,        { \"Xbox\", \"\" } },\n            { EControllerIcons::PlayStation, { \"PlayStation\", \"\" } }\n        }\n    }\n};\nCONFIG_DEFINE_LOCALE(LightDash)\n{\n    { ELanguage::English,  { \"Light Dash\", \"Change how Light Dash is activated for Sonic and Shadow.\" } },\n    { ELanguage::Japanese, { \"ライトダッシュ\", \"ソニックとシャドウのライトダッシュの発動方法を変更します\" } },\n    { ELanguage::German,   { \"Lichtsprint\", \"Ändere wie der Lichtsprint von Sonic und Shadow aktiviert wird.\" } },\n    { ELanguage::French,   { \"Course éclair\", \"Modifier la façon dont Course éclair est activée pour Sonic et Shadow.\" } },\n    { ELanguage::Spanish,  { \"Acelerón ligero\", \"Cambia cómo se activa el acelerón ligero para Sonic y Shadow.\" } },\n    { ELanguage::Italian,  { \"Super spinta\", \"Cambia il modo in cui viene attivata la Super spinta per Sonic e Shadow.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(ELightDash)\n{\n    {\n        ELanguage::English,\n        {\n            { ELightDash::X, { \"Press ${picture(button_x)}\", \"\" } },\n            { ELightDash::Y, { \"Press ${picture(button_y)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { ELightDash::X, { \"${picture(button_x)}を押す\", \"\" } },\n            { ELightDash::Y, { \"${picture(button_y)}を押す\", \"\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { ELightDash::X, { \"Drücke ${picture(button_x)}\", \"\" } },\n            { ELightDash::Y, { \"Drücke ${picture(button_y)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { ELightDash::X, { \"Appuyer sur ${picture(button_x)}\", \"\" } },\n            { ELightDash::Y, { \"Appuyer sur ${picture(button_y)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { ELightDash::X, { \"Pulsar ${picture(button_x)}\", \"\" } },\n            { ELightDash::Y, { \"Pulsar ${picture(button_y)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { ELightDash::X, { \"Premi ${picture(button_x)}\", \"\" } },\n            { ELightDash::Y, { \"Premi ${picture(button_y)}\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(SlidingAttack)\n{\n    { ELanguage::English,  { \"Sliding Attack\", \"Change how the Sliding Attack is activated for Sonic.\" } },\n    { ELanguage::Japanese, { \"スライディングアタック\", \"ソニックのスライディングアタックの発動方法を変更します\" } },\n    { ELanguage::German,   { \"Schlitterangriff\", \"Ändere wie der Schlitterangriff von Sonic aktiviert wird.\" } },\n    { ELanguage::French,   { \"Attaque-dérapage\", \"Modifier la façon dont l'Attaque-dérapage est activée pour Sonic.\" } },\n    { ELanguage::Spanish,  { \"Ataque derrape\", \"Cambia cómo se activa el ataque derrape para Sonic.\" } },\n    { ELanguage::Italian,  { \"Scivolata\", \"Cambia il modo in cui viene attivata la Scivolata per Sonic.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(ESlidingAttack)\n{\n    {\n        ELanguage::English,\n        {\n            { ESlidingAttack::B, { \"Hold ${picture(button_b)}\", \"\" } },\n            { ESlidingAttack::X, { \"Release ${picture(button_x)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { ESlidingAttack::B, { \"${picture(button_b)}を押し続ける\", \"\" } },\n            { ESlidingAttack::X, { \"${picture(button_x)}を離す\", \"\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { ESlidingAttack::B, { \"Halte ${picture(button_b)} gedrückt\", \"\" } },\n            { ESlidingAttack::X, { \"Lasse ${picture(button_x)} los\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { ESlidingAttack::B, { \"Maintenir ${picture(button_b)}\", \"\" } },\n            { ESlidingAttack::X, { \"Relâcher ${picture(button_x)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { ESlidingAttack::B, { \"Mantener ${picture(button_b)}\", \"\" } },\n            { ESlidingAttack::X, { \"Soltar ${picture(button_x)}\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { ESlidingAttack::B, { \"Tieni premuto ${picture(button_b)}\", \"\" } },\n            { ESlidingAttack::X, { \"Rilascia ${picture(button_x)}\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(MasterVolume)\n{\n    { ELanguage::English,  { \"Master Volume\", \"Adjust the overall volume.\" } },\n    { ELanguage::Japanese, { \"マスターボリューム\", \"全体ボリュームの大きさを調整できます\" } },\n    { ELanguage::German,   { \"Gesamtlautstärke\", \"Passe die Gesamtlautstärke an.\" } },\n    { ELanguage::French,   { \"Volume général\", \"Réglage du volume général.\" } },\n    { ELanguage::Spanish,  { \"Volumen maestro\", \"Ajusta el volumen general.\" } },\n    { ELanguage::Italian,  { \"Volume principale\", \"Regola il volume principale\" } }\n};\n\nCONFIG_DEFINE_LOCALE(MusicVolume)\n{\n    { ELanguage::English,  { \"Music Volume\", \"Adjust the volume for the music.\" } },\n    { ELanguage::Japanese, { \"BGMボリューム\", \"BGMボリュームの大きさを調整できます\" } },\n    { ELanguage::German,   { \"Musiklautstärke\", \"Passe die Lautstärke der Musik an.\" } },\n    { ELanguage::French,   { \"Volume de la musique\", \"Réglage du volume de la musique.\" } },\n    { ELanguage::Spanish,  { \"Volumen de la música\", \"Ajusta el volumen de la música.\" } },\n    { ELanguage::Italian,  { \"Volume musica di sottofondo\", \"Regola il volume della musica di sottofondo.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(EffectsVolume)\n{\n    { ELanguage::English,  { \"Effects Volume\", \"Adjust the volume for sound effects.\" } },\n    { ELanguage::Japanese, { \"SEボリューム\", \"SEボリュームの大きさを調整できます\" } },\n    { ELanguage::German,   { \"Soundeffektlautstärke\", \"Passe die Lautstärke der Soundeffekte an.\" } },\n    { ELanguage::French,   { \"Volume des effets sonores\", \"Réglage du volume des effets sonores.\" } },\n    { ELanguage::Spanish,  { \"Volumen de efectos\", \"Ajusta el volumen de los efectos de sonido.\" } },\n    { ELanguage::Italian,  { \"Volume effetti sonori\", \"Regola il volume degli effetti sonori.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(ChannelConfiguration)\n{\n    { ELanguage::English,  { \"Channel Configuration\", \"Change the output mode for your audio device.\" } },\n    { ELanguage::Japanese, { \"チャンネル設定\", \"オーディオデバイスの出力モードを変更できます\" } },\n    { ELanguage::German,   { \"Kanalkonfiguration\", \"Ändere den Ausgabemodus für dein Audioausgabegerät.\" } },\n    { ELanguage::French,   { \"Configuration sortie audio\", \"Modifie le mode de sortie pour votre périphérique audio.\" } },\n    { ELanguage::Spanish,  { \"Configuración de canales\", \"Cambia el modo de salida para tu dispositivo de audio.\" } },\n    { ELanguage::Italian,  { \"Configurazione canali audio\", \"Modifica la modalità di output per il tuo dispositivo audio.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EChannelConfiguration)\n{\n    {\n        ELanguage::English,\n        {\n            { EChannelConfiguration::Stereo,   { \"Stereo\", \"\" } },\n            { EChannelConfiguration::Surround, { \"Surround\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { EChannelConfiguration::Stereo,   { \"ステレオ\", \"\" } },\n            { EChannelConfiguration::Surround, { \"サラウンド\", \"\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { EChannelConfiguration::Stereo,   { \"Stereo\", \"\" } },\n            { EChannelConfiguration::Surround, { \"Surround\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { EChannelConfiguration::Stereo,   { \"Stéréo\", \"\" } },\n            { EChannelConfiguration::Surround, { \"Surround\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { EChannelConfiguration::Stereo,   { \"Estéreo\", \"\" } },\n            { EChannelConfiguration::Surround, { \"Envolvente\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { EChannelConfiguration::Stereo,   { \"Stereo\", \"\" } },\n            { EChannelConfiguration::Surround, { \"Surround\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(MuteOnFocusLost)\n{\n    { ELanguage::English,  { \"Mute on Focus Lost\", \"Mute the game's audio when the window is not in focus.\" } },\n    { ELanguage::Japanese, { \"フォーカスロストミュート\", \"ウィンドウがフォーカスされていないときにゲームのオーディオをミュートします\" } },\n    { ELanguage::German,   { \"Stummstellen wenn nicht fokussiert\", \"Stellt das Audio des Spiels stumm solange das Fenster nicht im Fokus ist.\" } },\n    { ELanguage::French,   { \"Muet si fenêtre inactive\", \"Coupe le son du jeu lorsque la fenêtre n'est pas active.\" } },\n    { ELanguage::Spanish,  { \"Silenciar al perder el foco\", \"Silencia el audio del juego cuando la ventana no esté en primer plano.\" } },\n    { ELanguage::Italian,  { \"Audio muto con finestra inattiva\", \"Disattiva l'audio del gioco quando la finestra non è attiva.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(MusicAttenuation)\n{\n    { ELanguage::English,  { \"Music Attenuation\", \"Fade out the game's music when external media is playing.\" } },\n    { ELanguage::Japanese, { \"BGM減衰\", \"外部メディアを再生するとゲームの音楽をフェードアウトします\" } },\n    { ELanguage::German,   { \"Musikdämpfung\", \"Stelle die Musik des Spiels stumm während externe Medien abgespielt werden.\" } },\n    { ELanguage::French,   { \"Atténuation audio\", \"Abaisse le volume des musiques du jeu lorsqu'un média externe est en cours de lecture.\" } },\n    { ELanguage::Spanish,  { \"Atenuación de música\", \"Atenúa la música del juego cuando un reproductor multimedia se encuentra activo.\" } },\n    { ELanguage::Italian,  { \"Attenuazione musica\", \"Abbassa il volume della musica di sottofondo quando un'altra applicazione riproduce dei suoni.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(WindowSize)\n{\n    { ELanguage::English,  { \"Window Size\", \"Adjust the size of the game window in windowed mode.\" } },\n    { ELanguage::Japanese, { \"ウィンドウサイズ\", \"ウィンドウモードでのゲームのウィンドウサイズを調整できます\" } },\n    { ELanguage::German,   { \"Fenstergröße\", \"Ändere die Größe des Spielfensters im Fenstermodus.\" } },\n    { ELanguage::French,   { \"Taille de la fenêtre\", \"Modifie la taille de la fenêtre de jeu en mode fenêtré.\" } },\n    { ELanguage::Spanish,  { \"Tamaño de ventana\", \"Ajusta el tamaño de la ventana de juego.\" } },\n    { ELanguage::Italian,  { \"Dimensioni finestra\", \"Regola le dimensioni della finestra del gioco in modalità finestra.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(Monitor)\n{\n    { ELanguage::English,  { \"Monitor\", \"Change which monitor to display the game on.\" } },\n    { ELanguage::Japanese, { \"モニター選択\", \"ゲームを表示するモニターを変更できます\" } },\n    { ELanguage::German,   { \"Monitor\", \"Ändere auf welchem Monitor das Spiel angezeigt wird.\" } },\n    { ELanguage::French,   { \"Moniteur\", \"Change le moniteur sur lequel le jeu sera affiché.\" } },\n    { ELanguage::Spanish,  { \"Pantalla\", \"Cambia la pantalla en la cuál se muestra el juego.\" } },\n    { ELanguage::Italian,  { \"Schermo\", \"Cambia lo schermo su cui visualizzare il gioco.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(AspectRatio)\n{\n    { ELanguage::English,  { \"Aspect Ratio\", \"Change the aspect ratio.\" } },\n    { ELanguage::Japanese, { \"アスペクト比\", \"アスペクト比を変更できます\" } },\n    { ELanguage::German,   { \"Seitenverhältnis\", \"Verändere das Seitenverhältnis.\" } },\n    { ELanguage::French,   { \"Format d'image\", \"Modifie le format d'image.\" } },\n    { ELanguage::Spanish,  { \"Relación de aspecto\", \"Cambia la relación de aspecto.\" } },\n    { ELanguage::Italian,  { \"Rapporto d'aspetto\", \"Modifica il rapporto d'aspetto.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EAspectRatio)\n{\n    {\n        ELanguage::English,\n        {\n            { EAspectRatio::Auto,     { \"Auto\", \"Auto: the aspect ratio will dynamically adjust to the window size.\" } },\n            { EAspectRatio::Original, { \"Original\", \"Original: locks the game to a widescreen aspect ratio.\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { EAspectRatio::Auto,     { \"自動\", \"自動: アスペクト比はウィンドウサイズに合わせて調整されます\" } },\n            { EAspectRatio::Original, { \"オリジナル\", \"オリジナル: ワイドスクリーンのアスペクト比に固定されます\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { EAspectRatio::Auto,     { \"Auto\", \"Auto: Das Seitenverhältnis passt sich automatisch der Fenstergröße an.\" } },\n            { EAspectRatio::Original, { \"Original\", \"Original: Stellt das Spiel in einem Breitbildschirm-Format dar.\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { EAspectRatio::Auto,     { \"Auto\", \"Auto : le format d'image s'adapte automatiquement à la taille de la fenêtre.\" } },\n            { EAspectRatio::Original, { \"Original\", \"Original : force le jeu sur un format d'image large.\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { EAspectRatio::Auto,     { \"Auto\", \"Auto: la relación de aspecto se ajusta de forma dinámica al tamaño de la ventana.\" } },\n            { EAspectRatio::Original, { \"Original\", \"Original: muestra el juego en relación de aspecto de pantalla ancha.\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { EAspectRatio::Auto,     { \"Auto\", \"Auto: il rapporto d'aspetto verra cambiato automaticamente in base alle dimensioni della finestra.\" } },\n            { EAspectRatio::Original, { \"Originale\", \"Originale: blocca il gioco a un rapporto d'aspetto widescreen.\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(ResolutionScale)\n{\n    { ELanguage::English,  { \"Resolution Scale\", \"Adjust the internal resolution of the game.\" } },\n    { ELanguage::Japanese, { \"解像度スケール\", \"ゲームの内部解像度を調整できます\" } },\n    { ELanguage::German,   { \"Rendering-Auflösung\", \"Passe die Auflösung der internen Darstellung an.\" } },\n    { ELanguage::French,   { \"Échelle de rendu\", \"Modifie la résolution interne du jeu.\" } },\n    { ELanguage::Spanish,  { \"Escala de resolución\", \"Ajusta la resolución interna del juego.\" } },\n    { ELanguage::Italian,  { \"Scala risoluzione\", \"Regola la risoluzione interna del gioco.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(Fullscreen)\n{\n    { ELanguage::English,  { \"Fullscreen\", \"Toggle between borderless fullscreen or windowed mode.\" } },\n    { ELanguage::Japanese, { \"フルスクリーン\", \"ボーダーレスフルスクリーンかウィンドウモードを選択できます\" } },\n    { ELanguage::German,   { \"Vollbild\", \"Wechsle zwischen dem randlosen Vollbildmodus und dem Fenstermodus.\" } },\n    { ELanguage::French,   { \"Plein écran\", \"Alterne entre le mode plein écran sans bordures et le mode fenêtré.\" } },\n    { ELanguage::Spanish,  { \"Pantalla completa\", \"Cambia entre modo de pantalla completa o ventana.\" } },\n    { ELanguage::Italian,  { \"Schermo pieno\", \"Attiva/disattiva tra modalità finestra senza cornice e modalità finestra.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(VSync)\n{\n    { ELanguage::English,  { \"V-Sync\", \"Synchronize the game to the refresh rate of the display to prevent screen tearing.\" } },\n    { ELanguage::Japanese, { \"垂直同期\", \"垂直同期の設定を変更できます\" } },\n    { ELanguage::German,   { \"V-Sync\", \"Synchronisiere das Spiel mit der Bildwiederholrate deines Bildschirms um Bildverzerrungen zu vermeiden.\" } },\n    { ELanguage::French,   { \"V-Sync\", \"Synchronise le jeu avec la fréquence de rafraîchissement de l'écran pour éviter le screen tearing.\" } },\n    { ELanguage::Spanish,  { \"V-Sync\", \"Sincroniza el juego a la tasa de refresco de la pantalla para prevenir el rasgado de la imagen.\" } },\n    { ELanguage::Italian,  { \"V-Sync\", \"Sincronizza il gioco con la frequenza d'aggiornamento del display per evitare lo screen tearing.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(FPS)\n{\n    { ELanguage::English,  { \"FPS\", \"Set the max frame rate the game can run at. WARNING: this may introduce glitches at frame rates other than 60 FPS.\" } },\n    { ELanguage::Japanese, { \"フレームレート上限\", \"ゲームが実行できる最大フレームレートを設定します　警告:60FPS以外のフレームレートでは不具合が発生する可能性があります\" } },\n    { ELanguage::German,   { \"FPS\", \"Bestimmt die maximale Bildwiederholrate. WARNUNG: es können Fehler bei einer Bildwiederholrate über 60 FPS auftreten.\" } },\n    { ELanguage::French,   { \"IPS\", \"Limiter le nombre d'images par seconde. ATTENTION : Cela peut provoquer des bugs à des fréquences autres que 60 IPS.\" } },\n    { ELanguage::Spanish,  { \"FPS\", \"Establece la tasa máxima de fotogramas a la que puede ejecutarse el juego. ADVERTENCIA: esto puede provocar fallos en velocidades distintas a 60 FPS.\" } },\n    { ELanguage::Italian,  { \"FPS\", \"Imposta il framerate massimo del gioco. ATTENZIONE: questa opzione può causare problemi con dei framerate rate superiori a 60 FPS.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(Brightness)\n{\n    { ELanguage::English,  { \"Brightness\", \"Adjust the brightness level.\" } },\n    { ELanguage::Japanese, { \"明るさの設定\", \"明るさレベルを調整します\" } },\n    { ELanguage::German,   { \"Helligkeit\", \"Stelle die Helligkeit ein.\" } },\n    { ELanguage::French,   { \"Luminosité\", \"Modifier le niveau de luminosité.\" } },\n    { ELanguage::Spanish,  { \"Brillo\", \"Ajusta el nivel de brillo.\" } },\n    { ELanguage::Italian,  { \"Luminosità\", \"Regola il livello di luminosità.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(AntiAliasing)\n{\n    { ELanguage::English,  { \"Anti-Aliasing\", \"Adjust the amount of smoothing applied to jagged edges.\" } },\n    { ELanguage::Japanese, { \"アンチエイリアス\", \"アンチエイリアスの種類を選択できます\" } },\n    { ELanguage::German,   { \"Kantenglättung\", \"Passe die Menge an Kantenglättung an.\" } },\n    { ELanguage::French,   { \"Anticrénelage\", \"Ajuste le niveau d'anticrénelage appliqué aux bords des objets.\" } },\n    { ELanguage::Spanish,  { \"Anti-Aliasing\", \"Ajusta el nivel de suavizado aplicado a los dientes de sierra.\" } },\n    { ELanguage::Italian,  { \"Anti-Aliasing\", \"Regola la quantità di smussamento applicata ai bordi.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EAntiAliasing)\n{\n    {\n        ELanguage::English,\n        {\n            { EAntiAliasing::Off, { \"Off\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { EAntiAliasing::Off, { \"オフ\", \"\" } },\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { EAntiAliasing::Off, { \"Aus\", \"\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { EAntiAliasing::Off, { \"Non\", \"\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { EAntiAliasing::Off, { \"No\", \"\" } },\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { EAntiAliasing::Off, { \"No\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(TransparencyAntiAliasing)\n{\n    { ELanguage::English,  { \"Transparency Anti-Aliasing\", \"Apply anti-aliasing to alpha transparent textures.\" } },\n    { ELanguage::Japanese, { \"透明度のアンチエイリアス\", \"透過テクスチャにアンチエイリアスを適用します\" } },\n    { ELanguage::German,   { \"Transparenz-Kantenglättung\", \"Wende Kantenglättung auf Alpha-Transparenz-Texturen an.\" } },\n    { ELanguage::French,   { \"Anticrénelage de transparence\", \"Applique l'anticrénelage sur les textures transparentes.\" } },\n    { ELanguage::Spanish,  { \"Anti-Aliasing de transparencias\", \"Aplica antialiasing a las texturas transparentes.\" } },\n    { ELanguage::Italian,  { \"Anti-Aliasing su texture trasparenti\", \"Applica l'anti-aliasing alle texture trasparenti.\" } }\n};\n\nCONFIG_DEFINE_LOCALE(ShadowResolution)\n{\n    { ELanguage::English,  { \"Shadow Resolution\", \"Set the resolution of real-time shadows.\" } },\n    { ELanguage::Japanese, { \"影の解像度\", \"影の解像度を設定できます\" } },\n    { ELanguage::German,   { \"Schattenauflösung\", \"Stelle die Auflösung der Echtzeit-Schatten ein.\" } },\n    { ELanguage::French,   { \"Résolution des ombres\", \"Définit la résolution des ombres en temps réel.\" } },\n    { ELanguage::Spanish,  { \"Resolución de sombras\", \"Establece la resolución de las sombras en tiempo real.\" } },\n    { ELanguage::Italian,  { \"Risoluzione ombre\", \"Imposta la risoluzioni delle ombre in tempo reale.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EShadowResolution) {};\n\nCONFIG_DEFINE_LOCALE(ReflectionResolution)\n{\n    { ELanguage::English,  { \"Reflection Resolution\", \"Set the resolution of real-time reflections.\" } },\n    { ELanguage::Japanese, { \"反射解像度\", \"リアルタイム反射の解像度を設定します\" } },\n    { ELanguage::German,   { \"Reflektionsauflösung\", \"Bestimmt die Auflösung der Echtzeitreflektionen.\" } },\n    { ELanguage::French,   { \"Résolution des reflets\", \"Définir la résolution des reflets en temps réel.\" } },\n    { ELanguage::Spanish,  { \"Resolución de reflejos\", \"Establece la resolución de los reflejos en tiempo real.\" } },\n    { ELanguage::Italian,  { \"Risoluzione riflessi\", \"Imposta la risoluzione dei riflessi in tempo reale.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EReflectionResolution)\n{\n    {\n        ELanguage::English,\n        {\n            { EReflectionResolution::Eighth, { \"12.5%\", \"\" } },\n            { EReflectionResolution::Quarter, { \"25%\", \"\" } },\n            { EReflectionResolution::Half, { \"50%\", \"\" } },\n            { EReflectionResolution::Full, { \"100%\", \"\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(RadialBlur)\n{\n    { ELanguage::English,  { \"Radial Blur\", \"Change the quality of the radial blur.\" } },\n    { ELanguage::Japanese, { \"放射状ぼかし\", \"放射状ぼかしの品質を変更します\" } },\n    { ELanguage::German,   { \"Radiale Unschärfe\", \"Verändere die Qualität der radialen Unschärfe.\" } },\n    { ELanguage::French,   { \"Flou Radial\", \"Modifier la qualité du flou radial.\" } },\n    { ELanguage::Spanish,  { \"Desenfoque radial\", \"Cambia la calidad del desenfoque radial.\" } },\n    { ELanguage::Italian,  { \"Sfocatura radiale\", \"Modifica la qualità della sfocatura radiale.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(ERadialBlur)\n{\n    {\n        ELanguage::English,\n        {\n            { ERadialBlur::Off,      { \"Off\", \"\" } },\n            { ERadialBlur::Original, { \"Original\", \"\" } },\n            { ERadialBlur::Enhanced, { \"Enhanced\", \"Enhanced: uses more samples for smoother radial blur.\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { ERadialBlur::Off,      { \"オフ\", \"\" } },\n            { ERadialBlur::Original, { \"オリジナル\", \"\" } },\n            { ERadialBlur::Enhanced, { \"エンハンスド\", \"エンハンスド：より多くのサンプルを使用してよりスムーズな放射状ぼかしを実現します\" } }\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { ERadialBlur::Off,      { \"Aus\", \"\" } },\n            { ERadialBlur::Original, { \"Original\", \"\" } },\n            { ERadialBlur::Enhanced, { \"Verbessert\", \"Verbessert: Benutzt mehr Samples um eine weichere radiale Unschärfe zu erzeugen.\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { ERadialBlur::Off,      { \"Non\", \"\" } },\n            { ERadialBlur::Original, { \"Original\", \"\" } },\n            { ERadialBlur::Enhanced, { \"Amélioré\", \"Amélioré : utilise plus d'échantillons pour un flou radial plus lisse.\" } }\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { ERadialBlur::Off,      { \"No\", \"\" } },\n            { ERadialBlur::Original, { \"Original\", \"\" } },\n            { ERadialBlur::Enhanced, { \"Mejorado\", \"Mejorado: utiliza más muestras para un desenfoque radial más suave.\" } }\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { ERadialBlur::Off,      { \"No\", \"\" } },\n            { ERadialBlur::Original, { \"Originale\", \"\" } },\n            { ERadialBlur::Enhanced, { \"Migliorato\", \"Migliorato: utilizza più campioni per una sfocatura radiale più uniforme.\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(CutsceneAspectRatio)\n{\n    { ELanguage::English,  { \"Cutscene Aspect Ratio\", \"Change the aspect ratio of the real-time cutscenes.\" } },\n    { ELanguage::Japanese, { \"アスペクト比のカットシーン\", \"リアルタイムカットシーンのアスペクト比を変更できます\" } },\n    { ELanguage::German,   { \"Zwischensequenz-Seitenverhältnis\", \"Verändere das Seitenverhältnis der Echtzeit-Zwischensequenzen.\" } },\n    { ELanguage::French,   { \"Format des cinématiques\", \"Modifie le format d'image des cinématiques en temps réel.\" } },\n    { ELanguage::Spanish,  { \"Relación de aspecto de cinemáticas\", \"Cambia la relación de aspecto de las cinemáticas en tiempo real.\" } },\n    { ELanguage::Italian,  { \"Rapporto d'aspetto dei filmati\", \"Cambia il rapporto d'aspetto dei filmati in tempo reale.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(ECutsceneAspectRatio)\n{\n    {\n        ELanguage::English,\n        {\n            { ECutsceneAspectRatio::Original, { \"Original\", \"Original: locks cutscenes to their original 16:9 aspect ratio.\" } },\n            { ECutsceneAspectRatio::Unlocked, { \"Unlocked\", \"Unlocked: allows cutscenes to adjust their aspect ratio to the window size. WARNING: this will introduce visual oddities past the original 16:9 aspect ratio.\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { ECutsceneAspectRatio::Original, { \"オリジナル\", \"オリジナル: カットシーンを元の16:9のアスペクト比に固定します\" } },\n            { ECutsceneAspectRatio::Unlocked, { \"解除\", \"解除: カットシーンのアスペクト比をウィンドウサイズに合わせて調整します 警告: 元の16:9のアスペクト比を超えると視覚的な異常が発生します\" } },\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { ECutsceneAspectRatio::Original, { \"Original\", \"Original: Behält die Zwischensequenzen in ihrem originalen 16:9 Seitenverhältnis.\" } },\n            { ECutsceneAspectRatio::Unlocked, { \"Entsperrt\", \"Entsperrt: Erlaubt Zwischensequenzen ihr Seitenverhältnis der Fenstergröße anzupassen. WARNUNG: Diese Option kann zu visuellen Fehlern außerhalb der Grenzen des ursprünglichen Seitenverhältnisses führen.\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { ECutsceneAspectRatio::Original, { \"Original\", \"Original : force les cinématiques dans leur format 16:9 d'origine.\" } },\n            { ECutsceneAspectRatio::Unlocked, { \"Libre\", \"Libre : permet aux cinématiques d'adapter leur format d'image à la taille de la fenêtre. ATTENTION : au delà du format 16:9 d'origine, des bugs visuels apparaitront.\" } },\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { ECutsceneAspectRatio::Original, { \"Original\", \"Original: muestra las cinemáticas en su relación de aspecto original de 16:9.\" } },\n            { ECutsceneAspectRatio::Unlocked, { \"Desbloqueado\", \"Desbloqueado: permite que las cinemáticas ajusten su relación de aspecto al tamaño de la ventana. ADVERTENCIA: esto introducirá rarezas visuales más allá de la relación de aspecto original de 16:9.\" } },\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { ECutsceneAspectRatio::Original, { \"Originale\", \"Originale: blocca il rapporto d'aspetto dei filmati a 16:9.\" } },\n            { ECutsceneAspectRatio::Unlocked, { \"Sbloccato\", \"Sbloccato: il rapporto d'aspetto verrà regolato in base alle dimensioni della finestra. ATTENZIONE: questa opzione potrebbe causare dei problemi visivi se il rapporto d'aspetto è oltre 16:9.\" } }\n        }\n    }\n};\n\nCONFIG_DEFINE_LOCALE(UIAlignmentMode)\n{\n    { ELanguage::English,  { \"UI Alignment Mode\", \"Change how the UI aligns with the display.\" } },\n    { ELanguage::Japanese, { \"UIアライメントモード\", \"UIとディスプレイの配置を変更できます\" } },\n    { ELanguage::German,   { \"Benutzeroberflächenausrichtung\", \"Verändere wie die Benutzeroberfläche sich mit dem Bildschirm ausrichtet.\" } },\n    { ELanguage::French,   { \"Alignement de l'IU\", \"Modifie l'alignement de l'interface utilisateur sur l'écran.\" } },\n    { ELanguage::Spanish,  { \"Modo de alineamiento de UI\", \"Cambia la alineación de la interfaz de usuario con la pantalla.\" } },\n    { ELanguage::Italian,  { \"Modalità allineamento interfaccia\", \"Modifica come l'interfaccia si allinea con lo schermo.\" } }\n};\n\nCONFIG_DEFINE_ENUM_LOCALE(EUIAlignmentMode)\n{\n    {\n        ELanguage::English,\n        {\n            { EUIAlignmentMode::Edge,    { \"Edge\", \"Edge: the UI will align with the edges of the display.\" } },\n            { EUIAlignmentMode::Centre,  { \"Center\", \"Center: the UI will align with the center of the display.\" } }\n        }\n    },\n    {\n        ELanguage::Japanese,\n        {\n            { EUIAlignmentMode::Edge,    { \"エッジ\", \"エッジ: UIがディスプレイの端に揃います\" } },\n            { EUIAlignmentMode::Centre,  { \"センター\", \"センター: UIがディスプレイの中央に揃います\" } },\n        }\n    },\n    {\n        ELanguage::German,\n        {\n            { EUIAlignmentMode::Edge,    { \"Rand\", \"Rand: Die Benutzeroberfläche richtet sich zum Rand des Bildschirms aus.\" } },\n            { EUIAlignmentMode::Centre,  { \"Mitte\", \"Mitte: Die Benutzeroberfläche richtet sich zur Mitte des Bildschirms aus.\" } }\n        }\n    },\n    {\n        ELanguage::French,\n        {\n            { EUIAlignmentMode::Edge,    { \"Bord\", \"Bord : l'interface utilisateur sera alignée sur les bords de l'écran.\" } },\n            { EUIAlignmentMode::Centre,  { \"Centrée\", \"Centrée : l'interface utilisateur sera alignée sur le centre de l'écran.\" } },\n        }\n    },\n    {\n        ELanguage::Spanish,\n        {\n            { EUIAlignmentMode::Edge,    { \"Borde\", \"Borde: la interfaz de usuario se alineará con los bordes de la pantalla.\" } },\n            { EUIAlignmentMode::Centre,  { \"Centro\", \"Centro: la interfaz de usuario se alineará con el centro de la pantalla.\" } },\n        }\n    },\n    {\n        ELanguage::Italian,\n        {\n            { EUIAlignmentMode::Edge,    { \"Bordi\", \"Bordi: l'interfaccia si allineerà con i bordi dello schermo.\" } },\n            { EUIAlignmentMode::Centre,  { \"Centro\", \"Centro: l'interfaccia si allineerà con il centro dello schermo.\" } }\n        }\n    }\n};\n"
  },
  {
    "path": "MarathonRecomp/locale/locale.cpp",
    "content": "#include <user/config.h>\n#include <locale/locale.h>\n\n/*\n    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!! LOCALISATION NOTES !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n    - Ensure brand names are always presented on the same line.\n\n      Correct:\n      This is a string that contains a brand name like\n      Xbox 360, which is one of the two consoles to have a port of\n      SONIC THE HEDGEHOG.\n\n      Incorrect:\n      This is a string that contains a brand name like Xbox\n      360, which is one of the two consoles to have a port of SONIC THE\n      HEDGEHOG.\n\n    - Ensure your locale is added in the correct order following the language enum.\n\n      Correct:\n      {\n          { ELanguage::English,  \"Example\" },\n          { ELanguage::Japanese, \"Example\" },\n          { ELanguage::German,   \"Example\" },\n          { ELanguage::French,   \"Example\" },\n          { ELanguage::Spanish,  \"Example\" },\n          { ELanguage::Italian,  \"Example\" }\n      }\n\n      Incorrect:\n      {\n          { ELanguage::English,  \"Example\" },\n          { ELanguage::French,   \"Example\" },\n          { ELanguage::Spanish,  \"Example\" },\n          { ELanguage::German,   \"Example\" },\n          { ELanguage::Italian,  \"Example\" },\n          { ELanguage::Japanese, \"Example\" }\n      }\n\n    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n*/\n\nstd::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>> g_locale =\n{\n    {\n        \"Options_Header_Name\",\n        {\n            { ELanguage::English,  \"OPTIONS\" },\n            { ELanguage::Japanese, \"OPTIONS\" },\n            { ELanguage::German,   \"OPTIONEN\" },\n            { ELanguage::French,   \"OPTIONS\" },\n            { ELanguage::Spanish,  \"OPCIONES\" },\n            { ELanguage::Italian,  \"OPZIONI\" }\n        }\n    },\n    {\n        \"Options_Category_System\",\n        {\n            { ELanguage::English,  \"System settings\" },\n            { ELanguage::Japanese, \"システム設定\" },\n            { ELanguage::German,   \"System-Einstellungen\" },\n            { ELanguage::French,   \"Paramètres système\" },\n            { ELanguage::Spanish,  \"Configuración del sistema\" },\n            { ELanguage::Italian,  \"Impostazioni sistema\" }\n        }\n    },\n    {\n        \"Options_Desc_Category_System\",\n        {\n            { ELanguage::English,  \"Adjust system settings.\" },\n            { ELanguage::Japanese, \"システムの設定を変更します\" },\n            { ELanguage::German,   \"Verändere System-Einstellungen.\" },\n            { ELanguage::French,   \"Modifier les paramètres système.\" },\n            { ELanguage::Spanish,  \"Ajustar la configuración del sistema.\" },\n            { ELanguage::Italian,  \"Modifica le impostazioni di sistema.\" }\n        }\n    },\n    {\n        \"Options_Category_Input\",\n        {\n            { ELanguage::English,  \"Input settings\" },\n            { ELanguage::Japanese, \"入力設定\" },\n            { ELanguage::German,   \"Eingabe-Einstellungen\" },\n            { ELanguage::French,   \"Paramètres d'entrée\" },\n            { ELanguage::Spanish,  \"Configuración de entrada\" },\n            { ELanguage::Italian,  \"Impostazioni input\" }\n        }\n    },\n    {\n        \"Options_Desc_Category_Input\",\n        {\n            { ELanguage::English,  \"Adjust input settings.\" },\n            { ELanguage::Japanese, \"入力の設定を変更します\" },\n            { ELanguage::German,   \"Verändere Eingabe-Einstellungen.\" },\n            { ELanguage::French,   \"Modifier les paramètres d'entrée.\" },\n            { ELanguage::Spanish,  \"Ajustar la configuración de entrada.\" },\n            { ELanguage::Italian,  \"Modifica le impostazioni input.\" }\n        }\n    },\n    {\n        \"Options_Category_Audio\",\n        {\n            { ELanguage::English,  \"Audio settings\" },\n            { ELanguage::Japanese, \"オーディオ設定\" },\n            { ELanguage::German,   \"Audio-Einstellungen\" },\n            { ELanguage::French,   \"Paramètres audio\" },\n            { ELanguage::Spanish,  \"Configuración de audio\" },\n            { ELanguage::Italian,  \"Impostazioni audio\" }\n        }\n    },\n    {\n        \"Options_Desc_Category_Audio\",\n        {\n            { ELanguage::English,  \"Adjust audio settings.\" },\n            { ELanguage::Japanese, \"オーディオの設定を変更します\" },\n            { ELanguage::German,   \"Verändere Audio-Einstellungen.\" },\n            { ELanguage::French,   \"Modifier les paramètres audio.\" },\n            { ELanguage::Spanish,  \"Ajustar la configuración de audio.\" },\n            { ELanguage::Italian,  \"Modifica le impostazioni audio.\" }\n        }\n    },\n    {\n        \"Options_Category_Video\",\n        {\n            { ELanguage::English,  \"Video settings\" },\n            { ELanguage::Japanese, \"ビデオ設定\" },\n            { ELanguage::German,   \"Video-Einstellungen\" },\n            { ELanguage::French,   \"Paramètres vidéo\" },\n            { ELanguage::Spanish,  \"Configuración de vídeo\" },\n            { ELanguage::Italian,  \"Impostazioni video\" }\n        }\n    },\n    {\n        \"Options_Desc_Category_Video\",\n        {\n            { ELanguage::English,  \"Adjust video settings.\" },\n            { ELanguage::Japanese, \"ビデオの設定を変更します\" },\n            { ELanguage::German,   \"Verändere Video-Einstellungen.\" },\n            { ELanguage::French,   \"Modifier les paramètres vidéo.\" },\n            { ELanguage::Spanish,  \"Ajustar la configuración de vídeo.\" },\n            { ELanguage::Italian,  \"Modifica le impostazioni video.\" }\n        }\n    },\n    {\n        \"Options_Category_Debug\",\n        {\n            { ELanguage::English,  \"Debug settings\" },\n            { ELanguage::Japanese, \"デバッグ設定\" },\n            { ELanguage::German,   \"Debug-Einstellungen\" },\n            { ELanguage::French,   \"Paramètres de débogage\" },\n            { ELanguage::Spanish,  \"Configuración de depuración\" },\n            { ELanguage::Italian,  \"Impostazioni debug\" }\n        }\n    },\n    {\n        \"Options_Desc_Category_Debug\",\n        {\n            { ELanguage::English,  \"Adjust debug settings.\" },\n            { ELanguage::Japanese, \"デバッグの設定を変更します\" },\n            { ELanguage::German,   \"Verändere Debug-Einstellungen.\" },\n            { ELanguage::French,   \"Modifier les paramètres de débogage.\" },\n            { ELanguage::Spanish,  \"Ajustar la configuración de depuración.\" },\n            { ELanguage::Italian,  \"Modifica le impostazioni debug.\" }\n        }\n    },\n    {\n        // Notes: integer values in the options menu (e.g. FPS) when at their maximum value.\n        \"Options_Value_Max\",\n        {\n            { ELanguage::English,  \"MAX\" },\n            { ELanguage::Japanese, \"MAX\" },\n            { ELanguage::German,   \"MAX\" },\n            { ELanguage::French,   \"MAX\" },\n            { ELanguage::Spanish,  \"MÁX\" },\n            { ELanguage::Italian,  \"MAX\" }\n        }\n    },\n    {\n        \"Options_Name_WindowSize\",\n        {\n            { ELanguage::English,  \"Window Size\" },\n            { ELanguage::Japanese, \"ウィンドウサイズ\" },\n            { ELanguage::German,   \"Fenstergröße\" },\n            { ELanguage::French,   \"Taille de la fenêtre\" },\n            { ELanguage::Spanish,  \"Tamaño de ventana\" },\n            { ELanguage::Italian,  \"Dimensioni della finestra\" }\n        }\n    },\n    {\n        \"Options_Desc_WindowSize\",\n        {\n            { ELanguage::English,  \"Adjust the size of the game window in windowed mode.\" },\n            { ELanguage::Japanese, \"ゲームのウィンドウサイズを設定できます\" },\n            { ELanguage::German,   \"Passe die Fenstergröße des Spiels im Fenstermodus an.\" },\n            { ELanguage::French,   \"Définir la résolution de jeu en mode fenêtré.\" },\n            { ELanguage::Spanish,  \"Ajusta el tamaño de la ventana de juego en modo ventana.\" },\n            { ELanguage::Italian,  \"Regola la dimensione della finestra del gioco in modalità finestra.\" }\n        }\n    },\n    {\n        // Notes: description for options that cannot be accessed during gameplay (e.g. Language).\n        \"Options_Desc_NotAvailable\",\n        {\n            { ELanguage::English,  \"This option is not available during gameplay.\" },\n            { ELanguage::Japanese, \"このオプションはゲームプレイ中は使用できません\" },\n            { ELanguage::German,   \"Diese Einstellung kann während des Spiels nicht verändert werden.\" },\n            { ELanguage::French,   \"Cette option est indisponible en cours de jeu.\" },\n            { ELanguage::Spanish,  \"Esta opción no está disponible durante la partida.\" },\n            { ELanguage::Italian,  \"Questa opzione non è disponibile durante il gioco.\" }\n        }\n    },\n    {\n        // Notes: description for options that are not implemented yet in development builds.\n        \"Options_Desc_NotImplemented\",\n        {\n            { ELanguage::English,  \"This option is not implemented yet.\" },\n            { ELanguage::Japanese, \"このオプションはまだ実装されていません\" },\n            { ELanguage::German,   \"Diese Einstellung wurde noch nicht implementiert.\" },\n            { ELanguage::French,   \"Cette option n'est pas encore implémentée.\" },\n            { ELanguage::Spanish,  \"Esta opción aún no está implementada.\" },\n            { ELanguage::Italian,  \"Questa opzione non è ancora implementata.\" }\n        }\n    },\n    {\n        // Notes: currently the description for Window Size when in fullscreen.\n        \"Options_Desc_NotAvailableFullscreen\",\n        {\n            { ELanguage::English,  \"This option is not available in fullscreen mode.\" },\n            { ELanguage::Japanese, \"このオプションはフルスクリーンモードでは変更できません\" },\n            { ELanguage::German,   \"Diese Option ist im Vollbildmodus nicht verfügbar.\" },\n            { ELanguage::French,   \"Cette option n'est pas disponible en mode plein écran.\" },\n            { ELanguage::Spanish,  \"Esta opción no está disponible en modo pantalla completa.\" },\n            { ELanguage::Italian,  \"Questa opzione non è disponibile in modalità schermo pieno.\" }\n        }\n    },\n    {\n        // Notes: currently the description for Monitor when in fullscreen.\n        \"Options_Desc_NotAvailableWindowed\",\n        {\n            { ELanguage::English,  \"This option is not available in windowed mode.\" },\n            { ELanguage::Japanese, \"このオプションはウィンドウモードでは変更できません\" },\n            { ELanguage::German,   \"Diese Option ist im Fenstermodus nicht verfügbar.\" },\n            { ELanguage::French,   \"Cette option n'est pas disponible en mode fenêtré.\" },\n            { ELanguage::Spanish,  \"Esta opción no está disponible en modo ventana.\" },\n            { ELanguage::Italian,  \"Questa opzione non è disponibile in modalità finestra.\" }\n        }\n    },\n    {\n        // Notes: currently the description for Monitor when the user only has one display connected.\n        \"Options_Desc_NotAvailableHardware\",\n        {\n            { ELanguage::English,  \"This option is not available with your current hardware configuration.\" },\n            { ELanguage::Japanese, \"このオプションは現在のハードウェア構成で変更できません\" },\n            { ELanguage::German,   \"Diese Option ist mit der momentanen Hardwarekonfiguration nicht verfügbar.\" },\n            { ELanguage::French,   \"Cette option n'est pas disponible avec votre configuration matérielle actuelle.\" },\n            { ELanguage::Spanish,  \"Esta opción no está disponible con tu configuración actual de hardware.\" },\n            { ELanguage::Italian,  \"Questa opzione non è disponibile con l'hardware in tuo possesso.\" }\n        }\n    },\n    {\n        // Notes: description for Transparency Anti-Aliasing when MSAA is disabled.\n        \"Options_Desc_NotAvailableMSAA\",\n        {\n            { ELanguage::English,  \"This option is not available without MSAA.\" },\n            { ELanguage::Japanese, \"このオプションはMSAAなしで変更できません\" },\n            { ELanguage::German,   \"Diese Option ist ohne MSAA nicht verfügbar.\" },\n            { ELanguage::French,   \"Cette option n'est pas disponible sans MSAA.\" },\n            { ELanguage::Spanish,  \"Esta opción no está disponible sin MSAA.\" },\n            { ELanguage::Italian,  \"Questa opzione non è disponibile senza MSAA.\" }\n        }\n    },\n    {\n        // Notes: description for Music Attenuation when the user is not running a supported OS.\n        \"Options_Desc_OSNotSupported\",\n        {\n            { ELanguage::English,  \"This option is not supported by your operating system.\" },\n            { ELanguage::Japanese, \"このオプションは現在のOSで変更できません\" },\n            { ELanguage::German,   \"Diese Option wird von diesem Betriebssystem nicht unterstützt.\" },\n            { ELanguage::French,   \"Cette option n'est pas prise en charge par votre système d'exploitation.\" },\n            { ELanguage::Spanish,  \"Esta opción no es compatible con tu sistema operativo.\" },\n            { ELanguage::Italian,  \"Questa opzione non è disponibile con il tuo sistema operativo.\" }\n        }\n    },\n    {\n        \"Options_Message_Restart\",\n        {\n            { ELanguage::English,  \"The game needs to restart to apply\\nthe following changes. OK?\\n\" },\n            { ELanguage::Japanese, \"以下の変更を適用するには\\nゲームを再起動する必要があります\\nよろしいですか？\\n\" },\n            { ELanguage::German,   \"Das Spiel muss neu gestartet werden um die\\nfolgenden Änderungen zu speichern. OK?\\n\" },\n            { ELanguage::French,   \"Le jeu doit redémarrer pour appliquer\\nles modifications suivantes. OK ?\\n\" },\n            { ELanguage::Spanish,  \"Se necesita reiniciar el juego para\\naplicar los siguientes cambios. ¿OK?\\n\" },\n            { ELanguage::Italian,  \"Il gioco deve essere riavviato per\\napplicare le seguenti modifiche. OK?\\n\" }\n        }\n    },\n    {\n        \"MainMenu_GoldMedalResults_Name\",\n        {\n            { ELanguage::English,  \"STATISTICS\" },\n            { ELanguage::Japanese, \"STATISTICS\" },\n            { ELanguage::German,   \"STATISTIKEN\" },\n            { ELanguage::French,   \"STATISTIQUES\" },\n            { ELanguage::Spanish,  \"ESTADÍSTICAS\" },\n            { ELanguage::Italian,  \"STATISTICHE\" }\n        }\n    },\n    {\n        \"MainMenu_GoldMedalResults_Description\",\n        {\n            { ELanguage::English,  \"Statistics: Displays lists of Gold Medals and Achievements\" },\n            { ELanguage::Japanese, \"スタティスティックス: ゴールドメダルと実績のリストを表示する\" },\n            { ELanguage::German,   \"Statistiken: Zeigt eine Liste von Gold Medaillen und Erfolgen an\" },\n            { ELanguage::French,   \"Statistiques : Affiche une liste des Médailles d'Or et des Accomplissements\" },\n            { ELanguage::Spanish,  \"Estadísticas: Muestra los listados de las medallas y los logros\" },\n            { ELanguage::Italian,  \"Statistiche: mostra gli elenchi delle medaglie d'oro e gli obiettivi\" }\n        }\n    },\n    {\n        \"Achievements_Title\",\n        {\n            { ELanguage::English,  \"Achievements\" },\n            { ELanguage::Japanese, \"実績\" },\n            { ELanguage::German,   \"Erfolge\" },\n            { ELanguage::French,   \"Accomplissements\" },\n            { ELanguage::Spanish,  \"Logros\" },\n            { ELanguage::Italian,  \"Obiettivi\" }\n        }\n    },\n    {\n        \"Achievements_Title_Uppercase\",\n        {\n            { ELanguage::English,  \"ACHIEVEMENTS\" },\n            { ELanguage::Japanese, \"実績\" },\n            { ELanguage::German,   \"ERFOLGE\" },\n            { ELanguage::French,   \"ACCOMPLISSEMENTS\" },\n            { ELanguage::Spanish,  \"LOGROS\" },\n            { ELanguage::Italian,  \"OBIETTIVI\" }\n        }\n    },\n    {\n        \"Achievements_GoldMedals\",\n        {\n            { ELanguage::English,  \"Gold Medals\" },\n            { ELanguage::Japanese, \"ゴールドメダル\" },\n            { ELanguage::German,   \"Gold Medaillen\" },\n            { ELanguage::French,   \"Médailles d'or\" },\n            { ELanguage::Spanish,  \"Medallas de oro\" },\n            { ELanguage::Italian,  \"Medaglie d'oro\" }\n        }\n    },\n    {\n        \"Achievements_GoldMedals_Uppercase\",\n        {\n            { ELanguage::English,  \"GOLD MEDALS\" },\n            { ELanguage::Japanese, \"ゴールドメダル\" },\n            { ELanguage::German,   \"GOLD MEDAILLEN\" },\n            { ELanguage::French,   \"MÉDAILLES D'OR\" },\n            { ELanguage::Spanish,  \"MEDALLAS DE ORO\" },\n            { ELanguage::Italian,  \"MEDAGLIE D'ORO\" }\n        }\n    },\n    {\n        \"Achievements_Unlock\",\n        {\n            { ELanguage::English,  \"Achievement Unlocked!\" },\n            { ELanguage::Japanese, \"実績のロックが解除されました\" },\n            { ELanguage::German,   \"Erfolg Freigeschaltet!\" },\n            { ELanguage::French,   \"Succès déverrouillé !\" },\n            { ELanguage::Spanish,  \"¡Logro desbloqueado!\" },\n            { ELanguage::Italian,  \"Obiettivo sbloccato!\" }\n        }\n    },\n    {\n        \"Achievements_Progress\",\n        {\n            { ELanguage::English,  \"PROGRESS %d/%d\" },\n            { ELanguage::Japanese, \"PROGRESS %d/%d\" },\n            { ELanguage::German,   \"FORTSCHRITT %d/%d\" },\n            { ELanguage::French,   \"PROGRESSION %d/%d\" },\n            { ELanguage::Spanish,  \"PROGRESO %d/%d\" },\n            { ELanguage::Italian,  \"PROGRESSI %d/%d\" }\n        }\n    },\n    {\n        // Locale required for font atlas generation.\n        \"Installer_MusicCredits\",\n        {\n            { ELanguage::English,  \"♬ Result & Chill Lofi - Hotline Sehwani & SilverIceSound\" },\n        },\n    },\n    {\n        \"Installer_Header_Installer\",\n        {\n            { ELanguage::English,  \"INSTALLER\" },\n            { ELanguage::Japanese, \"INSTALL\" },\n            { ELanguage::German,   \"INSTALLATION\" },\n            { ELanguage::French,   \"INSTALLATEUR\" },\n            { ELanguage::Spanish,  \"INSTALADOR\" },\n            { ELanguage::Italian,  \"INSTALLATORE\" },\n        },\n    },\n    {\n        \"Installer_Header_Installing\",\n        {\n            { ELanguage::English,  \"INSTALLING\" },\n            { ELanguage::Japanese, \"INSTALL\" },\n            { ELanguage::German,   \"INSTALLATION\" },\n            { ELanguage::French,   \"INSTALLATION\" },\n            { ELanguage::Spanish,  \"INSTALANDO\" },\n            { ELanguage::Italian,  \"INSTALLANDO\" },\n        }\n    },\n    {\n        \"Installer_Page_SelectLanguage\",\n        {\n            { ELanguage::English,  \"Please select a language.\" },\n            { ELanguage::Japanese, \"言語を選択してください\" },\n            { ELanguage::German,   \"Bitte eine Sprache auswählen.\" },\n            { ELanguage::French,   \"Choisissez une langue.\" },\n            { ELanguage::Spanish,  \"Selecciona un idioma.\" },\n            { ELanguage::Italian,  \"Seleziona una lingua.\" }\n        }\n    },\n    {\n        \"Installer_Page_Introduction\",\n        {\n            { ELanguage::English,  \"Welcome to Marathon Recompiled!\\n\\nYou'll need an Xbox 360 copy of\\nSONIC THE HEDGEHOG in order to proceed with the installation.\" },\n            { ELanguage::Japanese, \"Marathon Recompiledへようこそ！\\n\\nインストールにはXbox 360版の\\n「ソニック・ザ・ヘッジホッグ」\\nが必要です\" },\n            { ELanguage::German,   \"Willkommen zu Marathon Recompiled!\\n\\nEs wird eine Xbox 360 Kopie von\\nSONIC THE HEDGEHOG benötigt um mit der Installation fortfahren zu können.\" },\n            { ELanguage::French,   \"Bienvenue sur Marathon Recompiled !\\n\\nVous aurez besoin d'une copie de\\nSONIC THE HEDGEHOG pour\\nXbox 360 pour procéder à l'installation.\" },\n            { ELanguage::Spanish,  \"¡Bienvenido a Marathon Recompiled!\\n\\nNecesitas una copia de\\nSONIC THE HEDGEHOG de\\nXbox 360 para continuar con la instalación.\" },\n            { ELanguage::Italian,  \"Benvenuto a Marathon Recompiled!\\n\\nDovrai avere una copia di\\nSONIC THE HEDGEHOG per la\\nXbox 360 per proseguire con l'installazione.\" }\n        }\n    },\n    {\n        \"Installer_Page_SelectGame\",\n        {\n            { ELanguage::English,  \"Add the sources for the game.\" },\n            { ELanguage::Japanese, \"ゲームのソースを追加\" },\n            { ELanguage::German,   \"Füge die Quellen für das Spiel.\" },\n            { ELanguage::French,   \"Ajouter les fichiers du jeu.\" },\n            { ELanguage::Spanish,  \"Añade las fuentes para el juego.\" },\n            { ELanguage::Italian,  \"Aggiungi le fonti per il gioco.\" }\n        }\n    },\n    {\n        \"Installer_Page_SelectDLC\",\n        {\n            { ELanguage::English,  \"Add the sources for the DLC.\" },\n            { ELanguage::Japanese, \"DLCのソースを追加\" },\n            { ELanguage::German,   \"Füge die Quellen für die Erweiterungen des Spiels hinzu.\" },\n            { ELanguage::French,   \"Ajouter les fichiers pour les DLCs.\" },\n            { ELanguage::Spanish,  \"Añade las fuentes para el DLC.\" },\n            { ELanguage::Italian,  \"Aggiungi le fonti per i DLC.\" }\n        }\n    },\n    {\n        \"Installer_Page_CheckSpace\",\n        {\n            { ELanguage::English,  \"The content will be installed to the program's folder.\\n\\n\" },\n            { ELanguage::Japanese, \"コンテンツはプログラムのフォルダに\\nインストールされます\\n\\n\" },\n            { ELanguage::German,   \"Der Inhalt wird in dem Ordner des Programms installiert.\\n\\n\" },\n            { ELanguage::French,   \"Le contenu sera installé dans le même dossier que le programme.\\n\\n\" },\n            { ELanguage::Spanish,  \"El contenido será instalado a la carpeta del programa.\\n\\n\" },\n            { ELanguage::Italian,  \"Il contenuto verrà installato nella cartella di questo programma.\\n\\n\" }\n        }\n    },\n    {\n        \"Installer_Page_Installing\",\n        {\n            { ELanguage::English,  \"Please wait while the content is being installed...\" },\n            { ELanguage::Japanese, \"コンテンツのインストール中はお待ち\\nください\" },\n            { ELanguage::German,   \"Bitte warten. Der Inhalt wird installiert...\" },\n            { ELanguage::French,   \"Veuillez patienter pendant l'installation du contenu...\" },\n            { ELanguage::Spanish,  \"Por favor, espera mientras el contenido se instala... \" },\n            { ELanguage::Italian,  \"Attendi mentre il contenuto viene installato... \" }\n        }\n    },\n    {\n        \"Installer_Page_InstallSucceeded\",\n        {\n            { ELanguage::English,  \"Installation complete!\\n\\nThis project is brought to you by:\" },\n            { ELanguage::Japanese, \"インストール完了！\\n\\nプロジェクト制作：\" },\n            { ELanguage::German,   \"Installation abgeschlossen!\\n\\nDieses Projekt wird präsentiert von:\" },\n            { ELanguage::French,   \"Installation terminée !\\n\\nCe projet vous est présenté par :\" },\n            { ELanguage::Spanish,  \"¡Instalación completada!\\n\\nEste proyecto ha sido posible gracias a:\" },\n            { ELanguage::Italian,  \"Installazione completata!\\n\\nQuesto progetto è stato creato da:\" }\n        }\n    },\n    {\n        \"Installer_Page_InstallFailed\",\n        {\n            { ELanguage::English,  \"Installation failed.\\n\\n\" },\n            { ELanguage::Japanese, \"インストールに失敗しました\\n\\n\" },\n            { ELanguage::German,   \"Installation fehlgeschlagen.\\n\\n\" },\n            { ELanguage::French,   \"L'installation a échoué.\\n\\n\" },\n            { ELanguage::Spanish,  \"La instalación falló.\\n\\n\" },\n            { ELanguage::Italian,  \"Installazione fallita.\\n\\n\" }\n        }\n    },\n    {\n        \"Installer_Step_Game\",\n        {\n            { ELanguage::English,  \"Game Data\" },\n            { ELanguage::Japanese, \"ゲームデータ\" },\n            { ELanguage::German,   \"Spieldaten\" },\n            { ELanguage::French,   \"Fichiers du jeu\" },\n            { ELanguage::Spanish,  \"Archivos del juego\" },\n            { ELanguage::Italian,  \"Dati del gioco\" }\n        }\n    },\n    {\n        \"Installer_Step_RequiredSpace\",\n        {\n            { ELanguage::English,  \"Required space: %2.2f GiB\" },\n            { ELanguage::Japanese, \"必要な容量: %2.2f GiB\" },\n            { ELanguage::German,   \"Benötigter Speicherplatz:\\n%2.2f GiB\\n\" },\n            { ELanguage::French,   \"Espace nécessaire : %2.2f Gio\" },\n            { ELanguage::Spanish,  \"Espacio necesario: %2.2f GiB\" },\n            { ELanguage::Italian,  \"Spazio necessario: %2.2f GiB\" }\n        }\n    },\n    {\n        \"Installer_Step_AvailableSpace\",\n        {\n            { ELanguage::English,  \"Available space: %2.2f GiB\" },\n            { ELanguage::Japanese, \"使用可能な容量: %2.2f GiB\" },\n            { ELanguage::German,   \"Verfügbarer Speicherplatz:\\n%2.2f GiB\\n\" },\n            { ELanguage::French,   \"Espace disponible : %2.2f Gio\" },\n            { ELanguage::Spanish,  \"Espacio disponible: %2.2f GiB\" },\n            { ELanguage::Italian,  \"Spazio disponibile: %2.2f GiB\" }\n        }\n    },\n    {\n        \"Installer_Button_Next\",\n        {\n            { ELanguage::English,  \"Next\" },\n            { ELanguage::Japanese, \"次へ\" },\n            { ELanguage::German,   \"Weiter\" },\n            { ELanguage::French,   \"Suivant\" },\n            { ELanguage::Spanish,  \"Siguiente\" },\n            { ELanguage::Italian,  \"Continua\" }\n        }\n    },\n    {\n        \"Installer_Button_Skip\",\n        {\n            { ELanguage::English,  \"Skip\" },\n            { ELanguage::Japanese, \"スキップ\" },\n            { ELanguage::German,   \"Überspringen\" },\n            { ELanguage::French,   \"Ignorer\" },\n            { ELanguage::Spanish,  \"Saltar\" },\n            { ELanguage::Italian,  \"Salta\" }\n        }\n    },\n    {\n        \"Installer_Button_AddFiles\",\n        {\n            { ELanguage::English,  \"Add Files\" },\n            { ELanguage::Japanese, \"ファイルを追加\" },\n            { ELanguage::German,   \"Dateien hinzufügen\" },\n            { ELanguage::French,   \"Ajouter des fichiers\" },\n            { ELanguage::Spanish,  \"Añadir archivos\" },\n            { ELanguage::Italian,  \"Aggiungi file\" }\n        }\n    },\n    {\n        \"Installer_Button_AddFolder\",\n        {\n            { ELanguage::English,  \"Add Folder\" },\n            { ELanguage::Japanese, \"フォルダを追加\" },\n            { ELanguage::German,   \"Ordner hinzufügen\" },\n            { ELanguage::French,   \"Ajouter un dossier\" },\n            { ELanguage::Spanish,  \"Añadir carpeta\" },\n            { ELanguage::Italian,  \"Aggiungi cartella\" }\n        }\n    },\n    {\n        // Notes: message appears when using the \"Add Files\" option and choosing any file that is not an Xbox 360 game dump.\n        \"Installer_Message_InvalidFilesList\",\n        {\n            { ELanguage::English,  \"The following selected files are invalid:\" },\n            { ELanguage::Japanese, \"選択した次のファイルは無効です：\" },\n            { ELanguage::German,   \"Die folgenden Dateien sind ungültig:\" },\n            { ELanguage::French,   \"Les fichiers suivants ne sont pas valides :\" },\n            { ELanguage::Spanish,  \"Los siguientes archivos no son válidos:\" },\n            { ELanguage::Italian,  \"I seguenti file non sono validi:\" }\n        }\n    },\n    {\n        // Notes: message appears in the event there are some invalid files after adding the DLC and moving onto the next step.\n        \"Installer_Message_InvalidFiles\",\n        {\n            { ELanguage::English,  \"Some of the files that have been provided are not valid. Please make sure all the specified files are correct and try again.\" },\n            { ELanguage::Japanese, \"提供されたファイルの一部が有効ではありません指定されたファイルがすべて正しいことを確認してもう一度お試しください\" },\n            { ELanguage::German,   \"Einige Dateien, die bereitgestellt wurden sind ungültig. Bitte stelle sicher, dass die angegebenen Dateien korrekt sind und versuche es erneut.\" },\n            { ELanguage::French,   \"Certains fichiers fournis ne sont pas valides. Veuillez vous assurer que tous les fichiers spécifiés sont corrects et réessayez.\" },\n            { ELanguage::Spanish,  \"Algunos de los archivos seleccionados no son válidos. Por favor, asegúrate de que todos los archivos son correctos e inténtalo de nuevo.\" },\n            { ELanguage::Italian,  \"Alcuni dei file che sono stati selezionati non sono validi. Assicurati che tutti i file sono quelli corretti e riprova.\" }\n        }\n    },\n    {\n        // Notes: message appears when clicking the \"Add Files\" option for the first time.\n        \"Installer_Message_FilePickerTutorial\",\n        {\n            { ELanguage::English,  \"Select a digital dump with content from the game.\\n\\nThese files can be obtained from your Xbox 360 hard drive by following the instructions on the GitHub page.\\n\\nFor choosing a folder with extracted and unmodified game files, use the \\\"Add Folder\\\" option instead.\" },\n            { ELanguage::Japanese, \"ゲームのコンテンツを含む デジタルダンプを選択してください\\n\\nこれらのファイルは GitHubページの指示に従って\\nXbox 360ハードドライブから取得できます\\n\\n抽出された変更されていないゲームファイルを含むフォルダーを選択するには代わりに「フォルダの追加」オプションを使用してください\" },\n            { ELanguage::German,   \"Wähle einen digitalen Dump von dem Spiel.\\n\\nDie Dateien können über die Festplatte deiner\\nXbox 360 erlangt werden. Folge hierfür den Anweisungen auf der GitHub Seite.\\n\\nUm einen Ordner mit unmodifizierten Spieldateien auszuwählen, benutze die \\\"Ordner hinzufügen\\\" Option stattdessen.\" },\n            { ELanguage::French,   \"Sélectionnez une copie dématérialisée avec le contenu du jeu de base.\\n\\nCes fichiers peuvent être obtenus à partir du disque dur de votre Xbox 360 en suivant les instructions de la page GitHub.\\n\\nPour choisir un dossier contenant les fichiers de jeu extraits et non modifiés, utilisez plutôt l'option \\\"Ajouter un dossier\\\".\" },\n            { ELanguage::Spanish,  \"Selecciona una copia digital con contenido del juego.\\n\\nPuedes obtener los archivos de tu disco duro de\\nXbox 360 siguiendo las instrucciones de la página de GitHub.\\n\\nPara elegir una carpeta con archivos extraídos sin modificar, utiliza la opción \\\"Añadir carpeta\\\".\" },\n            { ELanguage::Italian,  \"Seleziona una copia digitale con i contenuti del gioco.\\n\\nQuesti file possono essere ottenuti dall'hard drive della tua Xbox 360 seguendo le istruzioni sulla pagina GitHub.\\n\\nPer selezionare una cartella con file estratti e non modificati usa l'opzione \\\"Aggiungi cartella\\\".\" }\n        }\n    },\n    {\n        // Notes: message appears when clicking the \"Add Folder\" option for the first time.\n        \"Installer_Message_FolderPickerTutorial\",\n        {\n            { ELanguage::English,  \"Select a folder that contains the unmodified files that have been extracted from the game.\\n\\nThese files can be obtained from your Xbox 360 hard drive by following the instructions on the GitHub page.\\n\\nFor choosing a digital dump, use the \\\"Add Files\\\" option instead.\" },\n            { ELanguage::Japanese, \"ゲームから抽出された変更されていないファイルを含むフォルダを選択してください\\n\\nこれらのファイルは GitHubページの指示に従って\\nXbox 360ハードドライブから取得できます\\n\\nデジタルダンプを選択するには\\n代わりに「ファイルの追加」オプションを使用してください\" },\n            { ELanguage::German,   \"Wähle einen Ordner, der unmodifizierte Dateien, die vom Spiel extrahiert wurden enthält.\\n\\nDie Dateien können über die Festplatte deiner\\nXbox 360 erlangt werden. Folge hierfür den Anweisungen auf der GitHub Seite.\\n\\nUm einen digitalen Dump auszuwählen, benutze die \\\"Dateien hinzufügen\\\" Option stattdessen.\" },\n            { ELanguage::French,   \"Sélectionnez un dossier contenant les fichiers extraits du jeu de base.\\n\\nCes fichiers peuvent être obtenus à partir du disque dur de votre Xbox 360 en suivant les instructions de la page GitHub.\\n\\nPour choisir une copie dématérialisée, utilisez plutôt l'option \\\"Ajouter des fichiers\\\".\" },\n            { ELanguage::Spanish,  \"Selecciona una carpeta que contenga los archivos sin modificar extraídos del juego.\\n\\nPuedes obtener los archivos de tu disco duro de\\nXbox 360 siguiendo las instrucciones de la página de GitHub.\\n\\nPara elegir una copia digital, utiliza la opción \\\"Añadir archivos\\\".\" },\n            { ELanguage::Italian,  \"Seleziona una cartella che contiene i file non modificati che sono stati estratti dal gioco.\\n\\nQuesti file possono essere ottenuti dall'hard drive della tua Xbox 360 seguendo le istruzioni sulla pagina GitHub.\\n\\nPer selezionare una copia digitale usa l'opzione \\\"Aggiungi file\\\".\" }\n        }\n    },\n    {\n        // Notes: message appears when choosing the Install option at the title screen when the user is missing DLC content.\n        // TODO: adjust line breaks for new message window.\n        \"Installer_Message_TitleMissingDLC\",\n        {\n            { ELanguage::English,  \"This will restart the game to\\nallow you to install any DLC\\nthat you are missing.\\n\\nWould you like to install missing\\ncontent?\" },\n            { ELanguage::Japanese, \"これによりゲームが再起動し不足しているDLCを\\nインストールできるようになります\\n\\n不足しているコンテンツを\\nインストールしますか？\" },\n            { ELanguage::German,   \"Das Spiel wird neu gestartet\\num die Installation einer fehlenden\\nErweiterung zu ermöglichen.\\n\\nMöchtest du den fehlenden\\nInhalt installieren?\" },\n            { ELanguage::French,   \"Cela redémarrera le jeu pour vous\\npermettre d'installer les DLC\\nmanquants.\\n\\nSouhaitez-vous installer le\\ncontenu manquant ?\" },\n            { ELanguage::Spanish,  \"Esta opción reiniciará el juego\\npara permitirte instalar los DLC\\nque falten.\\n\\n¿Quieres instalar el contenido\\nque falta?\" },\n            { ELanguage::Italian,  \"Questa opzione riavviera il gioco\\nper farti installare qualsiasi DLC\\nche non hai installato.\\n\\nVuoi installare i DLC mancanti?\" }\n        }\n    },\n    {\n        // Notes: message appears when choosing the Install option at the title screen when the user is not missing any content.\n        // TODO: adjust line breaks for new message window.\n        \"Installer_Message_Title\",\n        {\n            { ELanguage::English,  \"This restarts the game to\\nallow you to install any DLC\\nthat you may be missing.\\n\\nYou are not currently\\nmissing any DLC.\\n\\nWould you like to proceed\\nanyway?\" },\n            { ELanguage::Japanese, \"これによりゲームが再起動され\\n不足しているDLCを\\nインストールできるようになります\\n\\n現在 不足しているDLCはありません\\n\\nそれでも続行しますか？\" },\n            { ELanguage::German,   \"Das Spiel wird neu gestartet\\num die Installation einer fehlenden\\nErweiterung zu ermöglichen.\\n\\nEs kann keine weitere Erweiterung\\ninstalliert werden.\\n\\nMöchtest du trotzdem fortfahren?\" },\n            { ELanguage::French,   \"Cela redémarrera le jeu pour vous\\npermettre d'installer les DLC\\nmanquants.\\n\\nIl ne vous manque aucun DLC.\\n\\nVoulez-vous quand même continuer ?\" },\n            { ELanguage::Spanish,  \"Esto reiniciará el juego\\npara permitirte instalar\\nlos DLC que falten.\\n\\nActualmente, no falta ningún\\nDLC por instalarse.\\n\\n¿Quieres continuar de todos\\nmodos?\" },\n            { ELanguage::Italian,  \"Questa opzione riavviera il gioco\\nper farti installare qualsiasi DLC\\nche non hai installato.\\n\\nHai già installato tutti i DLC.\\n\\nVuoi procedere comunque?\" }\n        }\n    },\n    {\n        // Notes: message appears when user chooses \"Quit\" on the first available installation screen.\n        \"Installer_Message_Quit\",\n        {\n            { ELanguage::English,  \"Exit the installer.\\nOK?\" },\n            { ELanguage::Japanese, \"インストーラーを終了します\\nよろしいですか？\" },\n            { ELanguage::German,   \"Die Installation verlassen.\\nOK?\" },\n            { ELanguage::French,   \"Quitter l'installateur.\\nOK ?\" },\n            { ELanguage::Spanish,  \"¿Salir del instalador?\" },\n            { ELanguage::Italian,  \"Esci dall'installatore.\\nOK?\" }\n        }\n    },\n    {\n        // Notes: message appears when user chooses \"Cancel\" during installation.\n        \"Installer_Message_Cancel\",\n        {\n            { ELanguage::English,  \"Cancel the installation.\\nOK?\" },\n            { ELanguage::Japanese, \"インストールをキャンセルします\\nよろしいですか？\" },\n            { ELanguage::German,   \"Die Installation abbrechen.\\nOK?\" },\n            { ELanguage::French,   \"Annuler l'installation.\\nOK ?\" },\n            { ELanguage::Spanish,  \"¿Cancelar la instalación?\" },\n            { ELanguage::Italian,  \"Annulla l'installazione.\\nOK?\" }\n        }\n    },\n    {\n        // Notes: message appears when pressing B at the title screen.\n        \"Title_Message_Quit\",\n        {\n            { ELanguage::English,  \"Exit the game.\\nOK?\" },\n            { ELanguage::Japanese, \"ゲームを終了します\\nよろしいですか？\" },\n            { ELanguage::German,   \"Das Spiel verlassen.\\nOK?\" },\n            { ELanguage::French,   \"Quitter le jeu.\\nOK ?\" },\n            { ELanguage::Spanish,  \"¿Salir del juego?\" },\n            { ELanguage::Italian,  \"Esci dal gioco.\\nOK?\" }\n        }\n    },\n    {\n        // Notes: message appears when SonicNextAchievementData.bin is corrupted (mismatching file size, bad signature, incorrect version or invalid checksum) upon loading save data.\n        // To make this occur, open the file in any editor and just remove a large chunk of data.\n        \"Title_Message_LoadAchievementDataCorrupt\",\n        {\n            { ELanguage::English,  \"Load failed. Achievement data is corrupted.\\nIf you continue your achievement progress will\\nbe lost.\" },\n            { ELanguage::Japanese, \"ロード失敗。業績のデータが破損している。\\nセーブしていない進行状況は失われます\" },\n            { ELanguage::German,   \"Laden fehlgeschlagen. Erfolgs dateien sind\\nkorrupt. Wenn du fortfährst wird dein Erfolgs\\nfortschritt gelöscht.\" },\n            { ELanguage::French,   \"Chargement échoué. Les données d'Accomplissements\\nsont corrompus. Si vous continuez, vos\\naccomplissements seront perdus.\" },\n            { ELanguage::Spanish,  \"Error al cargar. Los datos de los logros están\\ndañados. Si continúas, se perderá el progreso\\nde tus logros.\" },\n            { ELanguage::Italian,  \"Caricamento fallito. I dati degli obiettivi\\nsono danneggiati. Se continui perderai tutti\\ni tuoi obiettivi.\" }\n        }\n    },\n    {\n        // Notes: message appears when SonicNextAchievementData.bin cannot be loaded upon loading save data.\n        // To make this occur, lock the SonicNextAchievementData.bin file using an external program so that it cannot be accessed by the game.\n        \"Title_Message_LoadAchievementDataIOError\",\n        {\n            { ELanguage::English,  \"Load failed. Achievement data cannot be loaded.\\nIf you continue you will not be able to save\\nyour achievement progress.\" },\n            { ELanguage::Japanese, \"ロード失敗。業績のデータがロードできません。\\n進行状況は失われますセーブしません\" },\n            { ELanguage::German,   \"Laden fehlgeschlagen. Erfolgs dateien können nicht\\ngeladen werden. Wenn du fortfährst wirst du deinen\\nErfolgs fortschritt nicht speichern können.\" },\n            { ELanguage::French,   \"Chargement échoué. Les données d'Accomplissements\\nn'ont pas pu être chargé. Si vous continuez, vous ne\\npourrez pas sauvegarder vos accomplissements.\" },\n            { ELanguage::Spanish,  \"Error al cargar. No se pueden cargar los datos\\nde los logros. Si continúas, no podrás guardar\\nel progreso de tus logros.\" },\n            { ELanguage::Italian,  \"Caricamento fallito. Impossibile caricare i dati\\ndegli obiettivi. Se continui non potrai salvare\\ni tuoi obiettivi.\" }\n        }\n    },\n    {\n        // Notes: message appears when SonicNextAchievementData.bin cannot be saved upon saving save data.\n        // To make this occur, lock the SonicNextAchievementData.bin file using an external program so that it cannot be accessed by the game.\n        \"Title_Message_SaveAchievementDataIOError\",\n        {\n            { ELanguage::English,  \"Save failed. Achievement data cannot be saved.\\nIf you continue you will not be able to save\\nyour achievement progress.\" },\n            { ELanguage::Japanese, \"セーブ失敗。業績のデータがセーブできません。\\n進行状況は失われますセーブしません\" },\n            { ELanguage::German,   \"Speichern fehlgeschlagen. Erfolgs dateien können\\nnicht gespeichert werden. Wenn du fortfährst wirst\\ndu deinen Erfolgs fortschritt nicht speichern können.\" },\n            { ELanguage::French,   \"Sauvegarde échoué. Les données d'Accomplissements\\nn'ont pas pu être sauvegardé. Si vous continuez, vous ne\\npourrez pas sauvegarder vos accomplissements.\" },\n            { ELanguage::Spanish,  \"Error al guardar. No se pueden guardar los datos\\nde los logros. Si continúas, no podrás guardar\\ntu progreso en los logros.\" },\n            { ELanguage::Italian,  \"Salvataggio fallito. Impossibile salvare i dati\\ndegli obiettivi. Se continui non potrai salvare\\ni tuoi obiettivi.\" }\n        }\n    },\n    {\n        \"Title_Message_UpdateAvailable\",\n        {\n            { ELanguage::English,  \"An update is available!\\n\\nWould you like to visit the\\nreleases page to download it?\" },\n            { ELanguage::Japanese, \"アップデートが利用可能です\\n\\nリリースページにアクセスして\\nダウンロードしますか？\" },\n            { ELanguage::German,   \"Ein Update ist verfügbar!\\n\\nMöchtest du die Release-Seite\\nbesuchen um es herunterzuladen?\" },\n            { ELanguage::French,   \"Une mise à jour est disponible !\\n\\nVoulez-vous visiter la page\\ndes mises à jour pour la\\ntélécharger ?\" },\n            { ELanguage::Spanish,  \"¡Hay una actualización disponible!\\n\\n¿Quieres ir a la página\\npara descargarla?\" },\n            { ELanguage::Italian,  \"È disponibile un aggiornamento!\\n\\nVuoi visitare la pagina releases\\nper scaricarlo?\" }\n        }\n    },\n    {\n        \"Video_BackendError\",\n        {\n            { ELanguage::English,  \"Unable to create a D3D12 (Windows) or Vulkan backend.\\n\\nPlease make sure that:\\n\\n- Your system meets the minimum requirements.\\n- Your GPU drivers are up to date.\\n- Your operating system is on the latest version available.\" },\n            { ELanguage::Japanese, \"D3D12 (Windows)または\\nVulkanバックエンドを作成できません\\n\\n次の点を確認してください：\\n\\n※システムが最小要件を満たしている\\n※GPUドライバーが最新である\\n※オペレーティングシステムが最新バージョンである\" },\n            { ELanguage::German,   \"Es ist nicht möglich, ein D3D12 (Windows) oder Vulkan-Backend zu erstellen.\\n\\nBitte stelle sicher, dass:\\n\\n- Dein System die Mindestanforderungen erfüllt.\\n- Deine GPU-Treiber auf dem neuesten Stand sind.\\n- Dein Betriebssystem auf der neuesten verfügbaren Version ist.\" },\n            { ELanguage::French,   \"Impossible de créer un backend D3D12 (Windows) ou Vulkan.\\n\\nVeuillez vous assurer que :\\n\\n- Votre système répond aux critères minimums requis.\\n- Les pilotes de votre processeur graphique sont à jour.\\n- Votre système d'exploitation est à jour.\" },\n            { ELanguage::Spanish,  \"No se puede crear un entorno de D3D12 (Windows) o de Vulkan.\\n\\nPor favor, asegúrate de que:\\n\\n- Tu equipo cumple con los requisitos mínimos.\\n- Los drivers de tu tarjeta gráfica están actualizados.\\n- Tu sistema operativo está actualizado a la última versión.\\n\" },\n            { ELanguage::Italian,  \"Impossibile creare un backend D3D12 (Windows) o Vulkan.\\n\\nAssicurati che:\\n\\n- Il tuo sistema soddisfi i requisiti minimi.\\n- I driver della scheda grafica siano aggiornati.\\n- Il tuo sistema operativo sia aggiornato.\" }\n        }\n    },\n    {\n        \"System_Win32_MissingDLLs\",\n        {\n            { ELanguage::English,  \"The module \\\"%s\\\" could not be found.\\n\\nPlease make sure that:\\n\\n- You extracted this copy of Marathon Recompiled fully and not just the *.exe file.\\n- You are not running Marathon Recompiled from a *.zip file.\" },\n            { ELanguage::Japanese, \"モジュール\\\"%s\\\"が見つかりませんでした\\n\\n次の点を確認してください：\\n\\n※Marathon Recompiledの*.exeファイルだけを抽出していなく、 コピーを完全に抽出してること\\n※Marathon Recompiledを*.zipファイルから実行していないこと\" },\n            { ELanguage::German,   \"Das Modul \\\"%s\\\" konnte nicht gefunden werden.\\n\\nBitte stelle sicher, dass:\\n\\n- Diese Kopie von Marathon Recompiled vollständig entpackt wurde und nicht nur die *.exe-Datei.\\n- Marathon Recompiled nicht direkt aus einer *.zip-Datei ausgeführt wird.\" },\n            { ELanguage::French,   \"Le module \\\"%s\\\" n'a pas pu être trouvé.\\n\\nVeuillez vous assurer que :\\n\\n- Vous avez extrait Marathon Recompiled dans son entièreté et pas seulement le fichier *.exe.\\n- Vous n'exécutez pas Marathon Recompiled à partir d'un fichier *.zip.\" },\n            { ELanguage::Spanish,  \"No se pudo encontrar el módulo \\\"%s\\\".\\n\\nAsegúrese de que:\\n\\n- Ha extraido esta copia de Marathon Recompiled por completo y no solo el archivo *.exe.\\n- No está ejecutando Marathon Recompiled desde un archivo *.zip.\" },\n            { ELanguage::Italian,  \"Impossibile trovare il modulo \\\"%s\\\".\\n\\nAssicurati che:\\n\\n- Hai estratto questa copia di Marathon Recompiled correttamente e non solo il file *.exe.\\n- Non stai eseguendo Marathon Recompiled da un file *.zip.\" }\n        }\n    },\n    {\n        \"System_MemoryAllocationFailed\",\n        {\n            { ELanguage::English,  \"Failed to allocate game memory.\\n\\nPlease make sure that:\\n\\n- You meet the minimum system requirements (8 GB).\\n- Your page file is configured with at least 4-8 GB of virtual memory.\" },\n            { ELanguage::Japanese, \"ゲームメモリの割り当てに失敗しました\\n\\n次の点を確認してください：\\n\\n※最小システム要件（8 GB）を満たしていること\\n※ページファイルに少なくとも4～8 GBの仮想メモリが設定されていること\" },\n            { ELanguage::German,   \"Fehler beim Zuweisen des Spielspeichers.\\n\\nBitte stelle sicher, dass:\\n\\n- Die Mindestanforderungen für das System erfüllt sind (8 GB).\\n- Die Auslagerungsdatei mit mindestens 4-8 GB virtuellem Speicher konfiguriert ist.\" },\n            { ELanguage::French,   \"Échec d'allocation de la mémoire du jeu.\\n\\nVeuillez vous assurer que :\\n\\n- Vous disposez de la configuration minimale requise (8 Go).\\n- Votre fichier d'échange est configuré avec au moins 4 à 8 Go de mémoire virtuelle.\" },\n            { ELanguage::Spanish,  \"Fallo al asignar memoria del juego.\\n\\nPor favor, asegúrate de que:\\n\\n- Cumples los requisitos mínimos del sistema (8 GB).\\n- Tu archivo de páginación está configurado con al menos 4 u 8 GB de memoria virtual.\" },\n            { ELanguage::Italian,  \"Impossibile allocare la memoria per il gioco.\\n\\nAssicurati che:\\n\\n- Soddisfi i requisiti minimi di sistema (8 GB).\\n- Il tuo file di paging sia configurato con almeno 4 o 8 GB di memoria virtuale.\" }\n        }\n    },\n    {\n        \"IntegrityCheck_Success\",\n        {\n            { ELanguage::English,  \"Installation check has finished.\\n\\nAll files seem to be correct.\\n\\nThe game will now close. Remove the launch argument to play the game.\" },\n            { ELanguage::Japanese, \"インストールチェックが終了しました\\n\\nすべてのファイルは正しいようです\\n\\nゲームは終了します、ゲームをプレイするには起動引数を削除してください\" },\n            { ELanguage::German,   \"Die Installation wurde überprüft.\\n\\nAlle Dateien scheinen korrekt zu sein.\\n\\nDas Spiel wird nun geschlossen. Entferne die Startoption, um das Spiel zu spielen.\" },\n            { ELanguage::French,   \"La vérification de l'installation est terminée.\\n\\nTous les fichiers semblent corrects.\\n\\nL'application va maintenant se fermer. Retirez l'argument de lancement pour pouvoir lancer le jeu.\" },\n            { ELanguage::Spanish,  \"La verificación de la instalación ha terminado.\\n\\nTodos los archivos parecen correctos.\\n\\nEl juego se cerrará ahora. Elimina el argumento de lanzamiento para jugar al juego.\" },\n            { ELanguage::Italian,  \"La verifica dei file d'installazione è terminata.\\n\\nTutti i file sembrano corretti.\\n\\nIl gioco si chiuderà. Rimuovi l'argomento di avvio per poter giocare.\" }\n        }\n    },\n    {\n        \"IntegrityCheck_Failed\",\n        {\n            { ELanguage::English,  \"Installation check has failed.\\n\\nError: %s\\n\\nThe game will now close. Try reinstalling the game by using the --install launch argument.\" },\n            { ELanguage::Japanese, \"インストールチェックに失敗しました\\n\\nエラー：%s\\n\\nゲームは終了します、--install 起動引数を使用してゲームを再インストールしてください\" },\n            { ELanguage::German,   \"Die Installationsprüfung ist fehlgeschlagen.\\n\\nFehler: %s\\n\\nDas Spiel wird nun geschlossen. Versuche das Spiel durch Verwendung der Startoption --install neu zu installieren.\" },\n            { ELanguage::French,   \"La vérification de l'installation a échoué.\\n\\nErreur : %s\\n\\nL'application va maintenant se fermer. Essayez de réinstaller le jeu en utilisant l'argument de lancement --install.\" },\n            { ELanguage::Spanish,  \"La verificación de la instalación ha fallado.\\n\\nError: %s\\n\\nEl juego se cerrará ahora. Intenta reinstalar el juego utilizando el argumento de lanzamiento --install.\" },\n            { ELanguage::Italian,  \"La verifica dei file d'installazione non è andata a buon fine.\\n\\nErrore: %s\\n\\nIl gioco si chiuderà. Prova a reinstallare il gioco utilizzando l'argomento di avvio --install.\" }\n        }\n    },\n    {\n        \"Common_OK\",\n        {\n            { ELanguage::English,  \"OK\" },\n            { ELanguage::Japanese, \"OK\" },\n            { ELanguage::German,   \"OK\" },\n            { ELanguage::French,   \"OK\" },\n            { ELanguage::Spanish,  \"OK\" },\n            { ELanguage::Italian,  \"OK\" }\n        }\n    },\n    {\n        \"Common_On\",\n        {\n            { ELanguage::English,  \"On\" },\n            { ELanguage::Japanese, \"オン\" },\n            { ELanguage::German,   \"An\" },\n            { ELanguage::French,   \"Oui\" },\n            { ELanguage::Spanish,  \"Act.\" },\n            { ELanguage::Italian,  \"Sì\" }\n        }\n    },\n    {\n        \"Common_Off\",\n        {\n            { ELanguage::English,  \"Off\" },\n            { ELanguage::Japanese, \"オフ\" },\n            { ELanguage::German,   \"Aus\" },\n            { ELanguage::French,   \"Non\" },\n            { ELanguage::Spanish,  \"Desact.\" },\n            { ELanguage::Italian,  \"No\" }\n        }\n    },\n    {\n        \"Common_Yes\",\n        {\n            { ELanguage::English,  \"Yes\" },\n            { ELanguage::Japanese, \"はい\" },\n            { ELanguage::German,   \"Ja\" },\n            { ELanguage::French,   \"Oui\" },\n            { ELanguage::Spanish,  \"Sí\" },\n            { ELanguage::Italian,  \"Sì\" }\n        }\n    },\n    {\n        \"Common_No\",\n        {\n            { ELanguage::English,  \"No\" },\n            { ELanguage::Japanese, \"いいえ\" },\n            { ELanguage::German,   \"Nein\" },\n            { ELanguage::French,   \"Non\" },\n            { ELanguage::Spanish,  \"No\" },\n            { ELanguage::Italian,  \"No\" }\n        }\n    },\n    {\n        \"Common_Next\",\n        {\n            { ELanguage::English,  \"Next\" },\n            { ELanguage::Japanese, \"次へ\" },\n            { ELanguage::German,   \"Weiter\" },\n            { ELanguage::French,   \"Suivant\" },\n            { ELanguage::Spanish,  \"Siguiente\" },\n            { ELanguage::Italian,  \"Avanti\" }\n        }\n    },\n    {\n        \"Common_Select\",\n        {\n            { ELanguage::English,  \"Select\" },\n            { ELanguage::Japanese, \"決定\" },\n            { ELanguage::German,   \"Auswählen\" },\n            { ELanguage::French,   \"Sélectionner\" },\n            { ELanguage::Spanish,  \"Seleccionar\" },\n            { ELanguage::Italian,  \"Seleziona\" }\n        }\n    },\n    {\n        \"Common_Back\",\n        {\n            { ELanguage::English,  \"Back\" },\n            { ELanguage::Japanese, \"戻る\" },\n            { ELanguage::German,   \"Zurück\" },\n            { ELanguage::French,   \"Retour\" },\n            { ELanguage::Spanish,  \"Atrás\" },\n            { ELanguage::Italian,  \"Indietro\" }\n        }\n    },\n    {\n        \"Common_Quit\",\n        {\n            { ELanguage::English,  \"Quit\" },\n            { ELanguage::Japanese, \"やめる\" },\n            { ELanguage::German,   \"Abbrechen\" },\n            { ELanguage::French,   \"Annuler\" },\n            { ELanguage::Spanish,  \"Cancelar\" },\n            { ELanguage::Italian,  \"Annulla\" }\n        }\n    },\n    {\n        \"Common_Cancel\",\n        {\n            { ELanguage::English,  \"Cancel\" },\n            { ELanguage::Japanese, \"キャンセル\" },\n            { ELanguage::German,   \"Abbrechen\" },\n            { ELanguage::French,   \"Annuler\" },\n            { ELanguage::Spanish,  \"Cancelar\" },\n            { ELanguage::Italian,  \"Annulla\" }\n        }\n    },\n    {\n        \"Common_Reset\",\n        {\n            { ELanguage::English,  \"Reset\" },\n            { ELanguage::Japanese, \"リセット\" },\n            { ELanguage::German,   \"Zurücksetzen\" },\n            { ELanguage::French,   \"Par défaut\" },\n            { ELanguage::Spanish,  \"Restablecer\" },\n            { ELanguage::Italian,  \"Ripristina\" }\n        }\n    },\n    {\n        \"Common_Switch\",\n        {\n            { ELanguage::English,  \"Switch\" },\n            { ELanguage::Japanese, \"きりかえ\" },\n            { ELanguage::German,   \"Wechseln\" },\n            { ELanguage::French,   \"Changer\" },\n            { ELanguage::Spanish,  \"Cambiar\" },\n            { ELanguage::Italian,  \"Cambia\" }\n        }\n    },\n    {\n        \"Common_Retry\",\n        {\n            { ELanguage::English,  \"Retry\" },\n            { ELanguage::Japanese, \"リトライ\" },\n            { ELanguage::German,   \"Wiederholen\" },\n            { ELanguage::French,   \"Réessayer\" },\n            { ELanguage::Spanish,  \"Reintentar\" },\n            { ELanguage::Italian,  \"Riprova\" }\n        }\n    },\n    {\n        \"Common_ContinueWithoutSaving\",\n        {\n            { ELanguage::English,  \"Continue without saving.\" },\n            { ELanguage::Japanese, \"セーブせずにゲームを続けます\" },\n            { ELanguage::German,   \"Fortsetzen ohne zu speichern.\" },\n            { ELanguage::French,   \"Continuer sans sauvegarder.\" },\n            { ELanguage::Spanish,  \"Continuar sin guardar.\" },\n            { ELanguage::Italian,  \"Continua senza salvare.\" }\n        }\n    },\n    {\n        \"Button_Cancel\",\n        {\n            { ELanguage::English, \"${picture(button_b)}${locale(Common_Cancel)}\" },\n            { ELanguage::Spanish, \"${picture(button_b)} ${locale(Common_Cancel)}\" }\n        }\n    },\n    {\n        \"Button_Back\",\n        {\n            { ELanguage::English, \"${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::Spanish, \"${picture(button_b)} ${locale(Common_Back)}\" }\n        }\n    },\n    {\n        \"Button_Select\",\n        {\n            { ELanguage::English, \"${picture(button_a)}${locale(Common_Select)}\" },\n            { ELanguage::Spanish, \"${picture(button_a)} ${locale(Common_Select)}\" },\n        }\n    },\n    {\n        \"Button_SelectQuit\",\n        {\n            { ELanguage::English, \"${picture(button_a)}${locale(Common_Select)}  ${picture(button_b)}${locale(Common_Quit)}\" },\n            { ELanguage::German,  \"${picture(button_a)}${locale(Common_Select)}　　${picture(button_b)}${locale(Common_Quit)}\" },\n            { ELanguage::Spanish, \"${picture(button_a)} ${locale(Common_Select)}  ${picture(button_b)} ${locale(Common_Quit)}\" },\n        }\n    },\n    {\n        \"Button_SelectBack\",\n        {\n            { ELanguage::English, \"${picture(button_a)}${locale(Common_Select)}  ${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::German,  \"${picture(button_a)}${locale(Common_Select)}　　${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::Spanish, \"${picture(button_a)} ${locale(Common_Select)}  ${picture(button_b)} ${locale(Common_Back)}\" },\n        }\n    },\n    {\n        \"Button_ResetSelectBack\",\n        {\n            { ELanguage::English, \"${picture(button_x)}${locale(Common_Reset)}  ${picture(button_a)}${locale(Common_Select)}  ${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::German,  \"${picture(button_x)}${locale(Common_Reset)}　　${picture(button_a)}${locale(Common_Select)}　　${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::Spanish, \"${picture(button_x)} ${locale(Common_Reset)}  ${picture(button_a)} ${locale(Common_Select)}  ${picture(button_b)} ${locale(Common_Back)}\" },\n        }\n    },\n    {\n        \"Button_GoldMedalsBack\",\n        {\n            { ELanguage::English, \"${picture(button_y)}${locale(Achievements_GoldMedals)}  ${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::German,  \"${picture(button_y)}${locale(Achievements_GoldMedals)}　　${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::Spanish, \"${picture(button_y)} ${locale(Achievements_GoldMedals)}  ${picture(button_b)} ${locale(Common_Back)}\" }\n        }\n    },\n    {\n        \"Button_AchievementsBack\",\n        {\n            { ELanguage::English, \"${picture(button_y)}${locale(Achievements_Title)}  ${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::German,  \"${picture(button_y)}${locale(Achievements_Title)}　　${picture(button_b)}${locale(Common_Back)}\" },\n            { ELanguage::Spanish, \"${picture(button_y)} ${locale(Achievements_Title)}  ${picture(button_b)} ${locale(Common_Back)}\" }\n        }\n    }\n};\n\nstd::string& Localise(const std::string_view& key)\n{\n    auto localeFindResult = g_locale.find(key);\n\n    if (localeFindResult != g_locale.end())\n    {\n        auto languageFindResult = localeFindResult->second.find(Config::Language);\n\n        if (languageFindResult == localeFindResult->second.end())\n            languageFindResult = localeFindResult->second.find(ELanguage::English);\n\n        if (languageFindResult != localeFindResult->second.end())\n            return languageFindResult->second;\n    }\n\n    return g_localeMissing;\n}\n"
  },
  {
    "path": "MarathonRecomp/locale/locale.h",
    "content": "#pragma once\n\nenum class ELanguage : uint32_t\n{\n    Unknown,\n    English,\n    Japanese,\n    German,\n    French,\n    Spanish,\n    Italian\n};\n\ninline std::string g_localeMissing = \"<missing string>\";\n\nextern std::unordered_map<std::string_view, std::unordered_map<ELanguage, std::string>> g_locale;\n\nstd::string& Localise(const std::string_view& key);\n"
  },
  {
    "path": "MarathonRecomp/main.cpp",
    "content": "#include <cstdio>\n#include <stdafx.h>\n#ifdef __x86_64__\n#include <cpuid.h>\n#endif\n#include <cpu/guest_thread.h>\n#include <gpu/video.h>\n#include <kernel/function.h>\n#include <kernel/memory.h>\n#include <kernel/heap.h>\n#include <kernel/xam.h>\n#include <kernel/io/file_system.h>\n#include <file.h>\n#include <vector>\n#include <image.h>\n#include <apu/audio.h>\n#include <hid/hid.h>\n#include <user/config.h>\n#include <user/paths.h>\n#include <user/registry.h>\n#include <kernel/xdbf.h>\n#include <install/installer.h>\n#include <install/update_checker.h>\n#include <os/logger.h>\n#include <os/process.h>\n#include <os/registry.h>\n#include <ui/game_window.h>\n#include <ui/installer_wizard.h>\n#include <mod/mod_loader.h>\n#include <preload_executable.h>\n#include <iostream>\n#include <app.h>\n\n#ifdef _WIN32\n#include <timeapi.h>\n#endif\n\n#if defined(_WIN32) && defined(MARATHON_RECOMP_D3D12)\nstatic std::array<std::string_view, 3> g_D3D12RequiredModules =\n{\n    \"D3D12/D3D12Core.dll\",\n    \"dxcompiler.dll\",\n    \"dxil.dll\"\n};\n#endif\n\nconst size_t XMAIOBegin = 0x7FEA0000;\nconst size_t XMAIOEnd = XMAIOBegin + 0x0000FFFF;\n\nMemory g_memory;\nHeap g_userHeap;\nXDBFWrapper g_xdbfWrapper;\nstd::unordered_map<uint16_t, GuestTexture*> g_xdbfTextureCache;\n\nvoid HostStartup()\n{\n#ifdef _WIN32\n    CoInitializeEx(nullptr, COINIT_MULTITHREADED);\n#endif\n\n    hid::Init();\n}\n\n// Name inspired from nt's entry point\nvoid KiSystemStartup()\n{\n    if (g_memory.base == nullptr)\n    {\n        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise(\"System_MemoryAllocationFailed\").c_str(), GameWindow::s_pWindow);\n        std::_Exit(1);\n    }\n\n    g_userHeap.Init();\n\n    const auto gameContent = XamMakeContent(XCONTENTTYPE_RESERVED, \"Game\");\n    const std::string gamePath = (const char*)(GetGamePath() / \"game\").u8string().c_str();\n\n    BuildPathCache(gamePath);\n\n    XamRegisterContent(gameContent, gamePath);\n\n    const auto saveFilePath = GetSaveFilePath(true);\n    bool saveFileExists = std::filesystem::exists(saveFilePath);\n\n    if (!saveFileExists)\n    {\n        // Copy base save data to modded save as fallback.\n        std::error_code ec;\n        std::filesystem::create_directories(saveFilePath.parent_path(), ec);\n\n        if (!ec)\n        {\n            std::filesystem::copy_file(GetSaveFilePath(false), saveFilePath, ec);\n            saveFileExists = !ec;\n        }\n    }\n\n    if (saveFileExists)\n    {\n        std::u8string savePathU8 = saveFilePath.parent_path().u8string();\n        XamRegisterContent(XamMakeContent(XCONTENTTYPE_SAVEDATA, \"SonicNextSaveData.bin\"), (const char*)(savePathU8.c_str()));\n    }\n\n    // Mount game\n    XamContentCreateEx(0, \"game\", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr);\n\n    // OS mounts game data to D:\n    XamContentCreateEx(0, \"D\", &gameContent, OPEN_EXISTING, nullptr, nullptr, 0, 0, nullptr);\n\n    std::error_code ec;\n    for (auto& file : std::filesystem::directory_iterator(GetGamePath() / \"dlc\", ec))\n    {\n        if (file.is_directory())\n        {\n            std::u8string fileNameU8 = file.path().filename().u8string();\n            std::u8string filePathU8 = file.path().u8string();\n            XamRegisterContent(XamMakeContent(XCONTENTTYPE_DLC, (const char*)(fileNameU8.c_str())), (const char*)(filePathU8.c_str()));\n        }\n    }\n\n    XAudioInitializeSystem();\n}\n\nuint32_t LdrLoadModule(const std::filesystem::path &path)\n{\n    const auto loadResult = LoadFile(path);\n    if (loadResult.empty())\n    {\n        assert(\"Failed to load module\" && false);\n        return 0;\n    }\n\n    const auto image = Image::ParseImage(loadResult.data(), loadResult.size());\n\n    memcpy(g_memory.Translate(image.base), image.data.get(), image.size);\n    g_xdbfWrapper = XDBFWrapper(static_cast<uint8_t*>(g_memory.Translate(image.resource_offset)), image.resource_size);\n\n    return image.entry_point;\n}\n\n#ifdef __x86_64__\n__attribute__((constructor(101), target(\"no-avx,no-avx2\"), noinline))\nvoid init()\n{\n    uint32_t eax, ebx, ecx, edx;\n\n    // Execute CPUID for processor info and feature bits.\n    __get_cpuid(1, &eax, &ebx, &ecx, &edx);\n\n    // Check for AVX support.\n    if ((ecx & (1 << 28)) == 0)\n    {\n        printf(\"[*] CPU does not support the AVX instruction set.\\n\");\n\n#ifdef _WIN32\n        MessageBoxA(nullptr, \"Your CPU does not meet the minimum system requirements.\", \"Marathon Recompiled\", MB_ICONERROR);\n#endif\n\n        std::_Exit(1);\n    }\n}\n#endif\n\nint main(int argc, char *argv[])\n{\n#ifdef _WIN32\n    timeBeginPeriod(1);\n#endif\n\n    os::process::CheckConsole();\n\n    if (!os::registry::Init())\n        LOGN_WARNING(\"OS does not support registry.\");\n\n    os::logger::Init();\n\n    PreloadContext preloadContext;\n    preloadContext.PreloadExecutable();\n\n    bool forceInstaller = false;\n    bool forceDLCInstaller = false;\n    bool useDefaultWorkingDirectory = false;\n    bool forceInstallationCheck = false;\n    bool graphicsApiRetry = false;\n    const char *sdlVideoDriver = nullptr;\n\n    for (uint32_t i = 1; i < argc; i++)\n    {\n        forceInstaller = forceInstaller || (strcmp(argv[i], \"--install\") == 0);\n        forceDLCInstaller = forceDLCInstaller || (strcmp(argv[i], \"--install-dlc\") == 0);\n        useDefaultWorkingDirectory = useDefaultWorkingDirectory || (strcmp(argv[i], \"--use-cwd\") == 0);\n        forceInstallationCheck = forceInstallationCheck || (strcmp(argv[i], \"--install-check\") == 0);\n        graphicsApiRetry = graphicsApiRetry || (strcmp(argv[i], \"--graphics-api-retry\") == 0);\n        App::s_isSkipLogos = App::s_isSkipLogos || (strcmp(argv[i], \"--skip-logos\") == 0);\n\n        if (strcmp(argv[i], \"--sdl-video-driver\") == 0)\n        {\n            if ((i + 1) < argc)\n                sdlVideoDriver = argv[++i];\n            else\n                LOGN_WARNING(\"No argument was specified for --sdl-video-driver. Option will be ignored.\");\n        }\n    }\n\n    if (!useDefaultWorkingDirectory)\n    {\n        // Set the current working directory to the executable's path.\n        std::error_code ec;\n        std::filesystem::current_path(os::process::GetExecutablePath().parent_path(), ec);\n    }\n\n    Config::Load();\n\n    if (forceInstallationCheck)\n    {\n        // Create the console to show progress to the user, otherwise it will seem as if the game didn't boot at all.\n        os::process::ShowConsole();\n\n        Journal journal;\n        double lastProgressMiB = 0.0;\n        double lastTotalMib = 0.0;\n        Installer::checkInstallIntegrity(GAME_INSTALL_DIRECTORY, journal, [&]()\n        {\n            constexpr double MiBDivisor = 1024.0 * 1024.0;\n            constexpr double MiBProgressThreshold = 128.0;\n            double progressMiB = double(journal.progressCounter) / MiBDivisor;\n            double totalMiB = double(journal.progressTotal) / MiBDivisor;\n            if (journal.progressCounter > 0)\n            {\n                if ((progressMiB - lastProgressMiB) > MiBProgressThreshold)\n                {\n                    fprintf(stdout, \"Checking files: %0.2f MiB / %0.2f MiB\\n\", progressMiB, totalMiB);\n                    lastProgressMiB = progressMiB;\n                }\n            }\n            else\n            {\n                if ((totalMiB - lastTotalMib) > MiBProgressThreshold)\n                {\n                    fprintf(stdout, \"Scanning files: %0.2f MiB\\n\", totalMiB);\n                    lastTotalMib = totalMiB;\n                }\n            }\n\n            return true;\n        });\n\n        char resultText[512];\n        uint32_t messageBoxStyle;\n        if (journal.lastResult == Journal::Result::Success)\n        {\n            snprintf(resultText, sizeof(resultText), \"%s\", Localise(\"IntegrityCheck_Success\").c_str());\n            fprintf(stdout, \"%s\\n\", resultText);\n            messageBoxStyle = SDL_MESSAGEBOX_INFORMATION;\n        }\n        else\n        {\n            snprintf(resultText, sizeof(resultText), Localise(\"IntegrityCheck_Failed\").c_str(), journal.lastErrorMessage.c_str());\n            fprintf(stderr, \"%s\\n\", resultText);\n            messageBoxStyle = SDL_MESSAGEBOX_ERROR;\n        }\n\n        SDL_ShowSimpleMessageBox(messageBoxStyle, GameWindow::GetTitle(), resultText, GameWindow::s_pWindow);\n        std::_Exit(int(journal.lastResult));\n    }\n\n#if defined(_WIN32) && defined(MARATHON_RECOMP_D3D12)\n    for (auto& dll : g_D3D12RequiredModules)\n    {\n        if (!std::filesystem::exists(g_executableRoot / dll))\n        {\n            char text[512];\n            snprintf(text, sizeof(text), Localise(\"System_Win32_MissingDLLs\").c_str(), dll.data());\n            SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), text, GameWindow::s_pWindow);\n            std::_Exit(1);\n        }\n    }\n#endif\n\n    // Check the time since the last time an update was checked. Store the new time if the difference is more than six hours.\n    constexpr double TimeBetweenUpdateChecksInSeconds = 6 * 60 * 60;\n    time_t timeNow = std::time(nullptr);\n    double timeDifferenceSeconds = difftime(timeNow, Config::LastChecked);\n    if (timeDifferenceSeconds > TimeBetweenUpdateChecksInSeconds)\n    {\n        UpdateChecker::initialize();\n        UpdateChecker::start();\n        Config::LastChecked = timeNow;\n        Config::Save();\n    }\n\n    if (Config::ShowConsole)\n        os::process::ShowConsole();\n    LOGN_WARNING(\"Host Startup\");\n    HostStartup();\n\n    std::filesystem::path modulePath;\n    bool isGameInstalled = Installer::checkGameInstall(GetGamePath(), modulePath);\n    bool runInstallerWizard = forceInstaller || forceDLCInstaller || !isGameInstalled;\n    if (runInstallerWizard)\n    {\n        if (!Video::CreateHostDevice(sdlVideoDriver, graphicsApiRetry))\n        {\n            SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise(\"Video_BackendError\").c_str(), GameWindow::s_pWindow);\n            std::_Exit(1);\n        }\n\n        if (!InstallerWizard::Run(GetGamePath(), isGameInstalled && forceDLCInstaller))\n            std::_Exit(0);\n    }\n\n    // ModLoader::Init();\n\n    KiSystemStartup();\n\n    uint32_t entry = LdrLoadModule(modulePath);\n\n    if (!runInstallerWizard)\n    {\n        if (!Video::CreateHostDevice(sdlVideoDriver, graphicsApiRetry))\n        {\n            SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, GameWindow::GetTitle(), Localise(\"Video_BackendError\").c_str(), GameWindow::s_pWindow);\n            std::_Exit(1);\n        }\n    }\n    LOGN_WARNING(\"Start Guest Thread\");\n    LOGN_WARNING(modulePath.string());\n    // Video::StartPipelinePrecompilation();\n\n    GuestThread::Start({ entry, 0, 0 });\n\n    return 0;\n}\n\nGUEST_FUNCTION_STUB(__imp__vsprintf);\nGUEST_FUNCTION_STUB(__imp___vsnprintf);\nGUEST_FUNCTION_STUB(__imp__sprintf);\nGUEST_FUNCTION_STUB(__imp___snprintf);\nGUEST_FUNCTION_STUB(__imp___snwprintf);\nGUEST_FUNCTION_STUB(__imp__vswprintf);\nGUEST_FUNCTION_STUB(__imp___vscwprintf);\nGUEST_FUNCTION_STUB(__imp__swprintf);\n"
  },
  {
    "path": "MarathonRecomp/misc_impl.cpp",
    "content": "#include \"stdafx.h\"\n#include <kernel/function.h>\n#include <kernel/xdm.h>\n\nuint32_t QueryPerformanceCounterImpl(LARGE_INTEGER* lpPerformanceCount)\n{\n    lpPerformanceCount->QuadPart = ByteSwap(std::chrono::steady_clock::now().time_since_epoch().count());\n    return TRUE;\n}\n\nuint32_t QueryPerformanceFrequencyImpl(LARGE_INTEGER* lpFrequency)\n{\n    constexpr auto Frequency = std::chrono::steady_clock::period::den / std::chrono::steady_clock::period::num;\n    lpFrequency->QuadPart = ByteSwap(Frequency);\n    return TRUE;\n}\n\nuint32_t GetTickCountImpl()\n{\n    return uint32_t(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count());\n}\n\nvoid GlobalMemoryStatusImpl(XLPMEMORYSTATUS lpMemoryStatus)\n{\n    lpMemoryStatus->dwLength = sizeof(XMEMORYSTATUS);\n    lpMemoryStatus->dwMemoryLoad = 0;\n    lpMemoryStatus->dwTotalPhys = 0x20000000;\n    lpMemoryStatus->dwAvailPhys = 0x20000000;\n    lpMemoryStatus->dwTotalPageFile = 0x20000000;\n    lpMemoryStatus->dwAvailPageFile = 0x20000000;\n    lpMemoryStatus->dwTotalVirtual = 0x20000000;\n    lpMemoryStatus->dwAvailVirtual = 0x20000000;\n}\n\n#ifndef _WIN32\nint memcpy_s(void* dest, size_t dest_size, const void* src, size_t count) {\n    if (dest == nullptr || src == nullptr) {\n        return EINVAL;\n    }\n    if (dest_size < count) {\n        return ERANGE;\n    }\n\n    memcpy(dest, src, count);\n    return 0;\n}\n#endif\n\nGUEST_FUNCTION_HOOK(sub_826DF680, memcpy);\n// GUEST_FUNCTION_HOOK(sub_831CCB98, memcpy);\n// GUEST_FUNCTION_HOOK(sub_831CEAE0, memcpy);\n// GUEST_FUNCTION_HOOK(sub_831CEE04, memcpy);\n// GUEST_FUNCTION_HOOK(sub_831CF2D0, memcpy);\n// GUEST_FUNCTION_HOOK(sub_831CF660, memcpy);\n// GUEST_FUNCTION_HOOK(sub_826DFAA0, memcpy);\nGUEST_FUNCTION_HOOK(sub_826DE940, memmove);\nGUEST_FUNCTION_HOOK(sub_826DFD40, memset);\nGUEST_FUNCTION_HOOK(sub_826DEA00, memcpy_s);\n// GUEST_FUNCTION_HOOK(sub_831CCAA0, memset);\n\n#ifdef _WIN32\nGUEST_FUNCTION_HOOK(sub_82537770, OutputDebugStringA);\n#else\nGUEST_FUNCTION_STUB(sub_82537770);\n#endif\n\nGUEST_FUNCTION_HOOK(sub_826FCE58, QueryPerformanceCounterImpl); // replaced\nGUEST_FUNCTION_HOOK(sub_826FC3C8, QueryPerformanceFrequencyImpl); // repalced\nGUEST_FUNCTION_HOOK(sub_826FD790, GetTickCountImpl); // replaced\n\n// GUEST_FUNCTION_HOOK(sub_82BD4BC0, GlobalMemoryStatusImpl);\n\n// sprintf\n// PPC_FUNC(sub_82BD4AE8)\n// {\n//     sub_831B1630(ctx, base);\n// }\n"
  },
  {
    "path": "MarathonRecomp/mod/ini_file.h",
    "content": "#pragma once\n\n#include <xxHashMap.h>\n\nclass IniFile\n{\nprotected:\n    struct Property\n    {\n        std::string name;\n        std::string value;\n    };\n\n    struct Section\n    {\n        std::string name;\n        xxHashMap<Property> properties;\n    };\n\n    xxHashMap<Section> m_sections;\n\n    static size_t hashStr(const std::string_view& str);\n\n    static bool isWhitespace(char value);\n    static bool isNewLine(char value);\n\npublic:\n    bool read(const std::filesystem::path& filePath);\n\n    std::string getString(const std::string_view& sectionName, const std::string_view& propertyName, std::string defaultValue) const;\n\n    bool getBool(const std::string_view& sectionName, const std::string_view& propertyName, bool defaultValue) const;\n\n    template<typename T>\n    T get(const std::string_view& sectionName, const std::string_view& propertyName, T defaultValue) const;\n\n    template<typename T>\n    void enumerate(const T& function) const;\n\n    template<typename T>\n    void enumerate(const std::string_view& sectionName, const T& function) const;\n\n    bool contains(const std::string_view& sectionName) const;\n};\n\n#include \"ini_file.inl\"\n"
  },
  {
    "path": "MarathonRecomp/mod/ini_file.inl",
    "content": "inline size_t IniFile::hashStr(const std::string_view& str)\n{\n    return XXH3_64bits(str.data(), str.size());\n}\n\ninline bool IniFile::isWhitespace(char value)\n{\n    return value == ' ' || value == '\\t';\n}\n\ninline bool IniFile::isNewLine(char value)\n{\n    return value == '\\n' || value == '\\r';\n}\n\ninline bool IniFile::read(const std::filesystem::path& filePath)\n{\n    std::ifstream file(filePath, std::ios::binary);\n    if (!file.good())\n        return false;\n\n    file.seekg(0, std::ios::end);\n\n    const size_t dataSize = static_cast<size_t>(file.tellg());\n    const auto data = std::make_unique<char[]>(dataSize + 1);\n    data[dataSize] = '\\0';\n\n    file.seekg(0, std::ios::beg);\n    file.read(data.get(), dataSize);\n\n    file.close();\n\n    Section* section = nullptr;\n    const char* dataPtr = data.get();\n\n    while (dataPtr < data.get() + dataSize)\n    {\n        if (*dataPtr == ';')\n        {\n            while (*dataPtr != '\\0' && !isNewLine(*dataPtr))\n                ++dataPtr;\n        }\n        else if (*dataPtr == '[')\n        {\n            ++dataPtr;\n            const char* endPtr = dataPtr;\n            while (*endPtr != '\\0' && !isNewLine(*endPtr) && *endPtr != ']')\n                ++endPtr;\n\n            if (*endPtr != ']')\n                return false;\n\n            std::string sectionName(dataPtr, endPtr - dataPtr);\n            section = &m_sections[hashStr(sectionName)];\n            section->name = std::move(sectionName);\n\n            dataPtr = endPtr + 1;\n        }\n        else if (!isWhitespace(*dataPtr) && !isNewLine(*dataPtr))\n        {\n            if (section == nullptr)\n                return false;\n\n            const char* endPtr;\n            if (*dataPtr == '\"')\n            {\n                ++dataPtr;\n                endPtr = dataPtr;\n\n                while (*endPtr != '\\0' && !isNewLine(*endPtr) && *endPtr != '\"')\n                    ++endPtr;\n\n                if (*endPtr != '\"')\n                    return false;\n            }\n            else\n            {\n                endPtr = dataPtr;\n\n                while (*endPtr != '\\0' && !isNewLine(*endPtr) && !isWhitespace(*endPtr) && *endPtr != '=')\n                    ++endPtr;\n\n                if (!isNewLine(*endPtr) && !isWhitespace(*endPtr) && *endPtr != '=')\n                    return false;\n            }\n\n            std::string propertyName(dataPtr, endPtr - dataPtr);\n            auto& property = section->properties[hashStr(propertyName)];\n            property.name = std::move(propertyName);\n\n            dataPtr = endPtr;\n            while (*dataPtr != '\\0' && !isNewLine(*dataPtr) && *dataPtr != '=')\n                ++dataPtr;\n\n            if (*dataPtr == '=')\n            {\n                ++dataPtr;\n\n                while (*dataPtr != '\\0' && isWhitespace(*dataPtr))\n                    ++dataPtr;\n\n                if (*dataPtr == '\"')\n                {\n                    ++dataPtr;\n                    endPtr = dataPtr;\n\n                    while (*endPtr != '\\0' && !isNewLine(*endPtr) && *endPtr != '\"')\n                        ++endPtr;\n\n                    if (*endPtr != '\"')\n                        return false;\n                }\n                else\n                {\n                    endPtr = dataPtr;\n\n                    while (*endPtr != '\\0' && !isNewLine(*endPtr) && !isWhitespace(*endPtr))\n                        ++endPtr;\n                }\n\n                property.value = std::string(dataPtr, endPtr - dataPtr);\n                dataPtr = endPtr + 1;\n            }\n        }\n        else\n        {\n            ++dataPtr;\n        }\n    }\n\n    return true;\n}\n\ninline std::string IniFile::getString(const std::string_view& sectionName, const std::string_view& propertyName, std::string defaultValue) const\n{\n    const auto sectionPair = m_sections.find(hashStr(sectionName));\n    if (sectionPair != m_sections.end())\n    {\n        const auto propertyPair = sectionPair->second.properties.find(hashStr(propertyName));\n        if (propertyPair != sectionPair->second.properties.end())\n            return propertyPair->second.value;\n    }\n    return defaultValue;\n}\n\ninline bool IniFile::getBool(const std::string_view& sectionName, const std::string_view& propertyName, bool defaultValue) const\n{\n    const auto sectionPair = m_sections.find(hashStr(sectionName));\n    if (sectionPair != m_sections.end())\n    {\n        const auto propertyPair = sectionPair->second.properties.find(hashStr(propertyName));\n        if (propertyPair != sectionPair->second.properties.end() && !propertyPair->second.value.empty())\n        {\n            const char firstChar = propertyPair->second.value[0];\n            return firstChar == 't' || firstChar == 'T' || firstChar == 'y' || firstChar == 'Y' || firstChar == '1';\n        }\n    }\n    return defaultValue;\n}\n\ninline bool IniFile::contains(const std::string_view& sectionName) const\n{\n    return m_sections.contains(hashStr(sectionName));\n}\n\ntemplate <typename T>\nT IniFile::get(const std::string_view& sectionName, const std::string_view& propertyName, T defaultValue) const\n{\n    const auto sectionPair = m_sections.find(hashStr(sectionName));\n    if (sectionPair != m_sections.end())\n    {\n        const auto propertyPair = sectionPair->second.properties.find(hashStr(propertyName));\n        if (propertyPair != sectionPair->second.properties.end())\n        {\n            T value{};\n            const auto result = std::from_chars(propertyPair->second.value.data(), \n                propertyPair->second.value.data() + propertyPair->second.value.size(), value);\n\n            if (result.ec == std::errc{})\n                return value;\n        }\n    }\n    return defaultValue;\n}\n\ntemplate<typename T>\ninline void IniFile::enumerate(const T& function) const\n{\n    for (const auto& [_, section] : m_sections)\n    {\n        for (auto& property : section.properties)\n            function(section.name, property.second.name, property.second.value);\n    }\n}\n\ntemplate <typename T>\nvoid IniFile::enumerate(const std::string_view& sectionName, const T& function) const\n{\n    const auto sectionPair = m_sections.find(hashStr(sectionName));\n    if (sectionPair != m_sections.end())\n    {\n        for (const auto& property : sectionPair->second.properties)\n            function(property.second.name, property.second.value);\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/mod/mod_loader.cpp",
    "content": "#include \"mod_loader.h\"\n#include \"ini_file.h\"\n\n#include <cpu/guest_stack_var.h>\n#include <kernel/function.h>\n#include <kernel/heap.h>\n#include <user/config.h>\n#include <user/paths.h>\n#include <os/logger.h>\n#include <os/process.h>\n#include <xxHashMap.h>\n\nenum class ModType\n{\n    HMM,\n    UMM\n};\n\nstruct Mod\n{\n    ModType type{};\n    std::vector<std::filesystem::path> includeDirs;\n    bool merge = false;\n    ankerl::unordered_dense::set<std::filesystem::path> readOnly;\n};\n\nstatic std::vector<Mod> g_mods;\n\nstd::filesystem::path ModLoader::ResolvePath(std::string_view path)\n{\n    std::string_view root;\n\n    size_t sepIndex = path.find(\":\\\\\");\n    if (sepIndex != std::string_view::npos)\n    {\n        root = path.substr(0, sepIndex);\n        path.remove_prefix(sepIndex + 2);\n    }\n\n    if (root == \"save\")\n    {\n        if (!ModLoader::s_saveFilePath.empty())\n        {\n            if (path == \"SYS-DATA\")\n                return ModLoader::s_saveFilePath;\n            else\n                return ModLoader::s_saveFilePath.parent_path() / path;\n        }\n\n        return {};\n    }\n\n    if (g_mods.empty())\n        return {};\n\n    thread_local xxHashMap<std::filesystem::path> s_cache;\n\n    XXH64_hash_t hash = XXH3_64bits(path.data(), path.size());\n    auto findResult = s_cache.find(hash);\n    if (findResult != s_cache.end())\n        return findResult->second;\n\n    std::string pathStr(path);\n    std::replace(pathStr.begin(), pathStr.end(), '\\\\', '/');\n    std::filesystem::path fsPath(std::move(pathStr));\n\n    bool canBeMerged = \n        path.find(\".arl\") == (path.size() - 4) ||\n        path.find(\".ar.\") == (path.size() - 6) ||\n        path.find(\".ar\") == (path.size() - 3);\n\n    for (auto& mod : g_mods)\n    {\n        if (mod.type == ModType::UMM && mod.merge && canBeMerged && !mod.readOnly.contains(fsPath))\n            continue;\n\n        for (auto& includeDir : mod.includeDirs)\n        {\n            std::filesystem::path modPath = includeDir / fsPath;\n            if (std::filesystem::exists(modPath))\n                return s_cache.emplace(hash, modPath).first->second;\n        }\n    }\n\n    return s_cache.emplace(hash, std::filesystem::path{}).first->second;\n}\n\nstd::vector<std::filesystem::path>* ModLoader::GetIncludeDirectories(size_t modIndex)\n{\n    return modIndex < g_mods.size() ? &g_mods[modIndex].includeDirs : nullptr;\n}\n\nvoid ModLoader::Init()\n{\n    const std::filesystem::path& userPath = GetUserPath();\n\n    IniFile configIni;\n    if (!configIni.read(userPath / \"cpkredir.ini\"))\n    {\n        configIni = {};\n\n        if (!configIni.read(GetGamePath() / \"cpkredir.ini\"))\n            return;\n    }\n\n    if (!configIni.getBool(\"CPKREDIR\", \"Enabled\", true))\n        return;\n\n    if (configIni.getBool(\"CPKREDIR\", \"EnableSaveFileRedirection\", false))\n    {\n        std::string saveFilePathU8 = configIni.getString(\"CPKREDIR\", \"SaveFileFallback\", \"\");\n        if (!saveFilePathU8.empty())\n            ModLoader::s_saveFilePath = std::u8string_view((const char8_t*)saveFilePathU8.c_str());\n        else\n            ModLoader::s_saveFilePath = userPath / \"mlsave\";\n\n        ModLoader::s_saveFilePath /= \"SYS-DATA\";\n    }\n\n    if (configIni.getString(\"CPKREDIR\", \"LogType\", std::string()) == \"console\")\n    {\n        os::process::ShowConsole();\n        s_isLogTypeConsole = true;\n    }\n\n    std::string modsDbIniFilePathU8 = configIni.getString(\"CPKREDIR\", \"ModsDbIni\", \"\");\n    if (modsDbIniFilePathU8.empty())\n        return;\n\n    IniFile modsDbIni;\n    if (!modsDbIni.read(std::u8string_view((const char8_t*)modsDbIniFilePathU8.c_str())))\n        return;\n\n    bool foundModSaveFilePath = false;\n\n    size_t activeModCount = modsDbIni.get<size_t>(\"Main\", \"ActiveModCount\", 0);\n    for (size_t i = 0; i < activeModCount; ++i)\n    {\n        std::string modId = modsDbIni.getString(\"Main\", fmt::format(\"ActiveMod{}\", i), \"\");\n        if (modId.empty())\n            continue;\n\n        std::string modIniFilePathU8 = modsDbIni.getString(\"Mods\", modId, \"\");\n        if (modIniFilePathU8.empty())\n            continue;\n\n        std::filesystem::path modIniFilePath(std::u8string_view((const char8_t*)modIniFilePathU8.c_str()));\n\n        IniFile modIni;\n        if (!modIni.read(modIniFilePath))\n            continue;\n\n        auto modDirectoryPath = modIniFilePath.parent_path();\n        std::string modSaveFilePathU8;\n\n        Mod mod;\n\n        if (modIni.contains(\"Details\") || modIni.contains(\"Filesystem\")) // UMM\n        {\n            mod.type = ModType::UMM;\n            mod.includeDirs.emplace_back(modDirectoryPath);\n            mod.merge = modIni.getBool(\"Details\", \"Merge\", modIni.getBool(\"Filesystem\", \"Merge\", false));\n\n            std::string readOnly = modIni.getString(\"Details\", \"Read-only\", modIni.getString(\"Filesystem\", \"Read-only\", std::string()));\n            std::replace(readOnly.begin(), readOnly.end(), '\\\\', '/');\n            std::string_view readOnlySplit = readOnly;\n\n            while (!readOnlySplit.empty())\n            {\n                size_t index = readOnlySplit.find(',');\n                if (index == std::string_view::npos)\n                {\n                    mod.readOnly.emplace(readOnlySplit);\n                    break;\n                }\n\n                mod.readOnly.emplace(readOnlySplit.substr(0, index));\n                readOnlySplit.remove_prefix(index + 1);\n            }\n\n            if (!foundModSaveFilePath)\n                modSaveFilePathU8 = modIni.getString(\"Details\", \"Save\", modIni.getString(\"Filesystem\", \"Save\", std::string()));\n        }\n        else // HMM\n        {\n            mod.type = ModType::HMM;\n\n            size_t includeDirCount = modIni.get<size_t>(\"Main\", \"IncludeDirCount\", 0);\n            for (size_t j = 0; j < includeDirCount; j++)\n            {\n                std::string includeDirU8 = modIni.getString(\"Main\", fmt::format(\"IncludeDir{}\", j), \"\");\n                if (!includeDirU8.empty())\n                {\n                    std::replace(includeDirU8.begin(), includeDirU8.end(), '\\\\', '/');\n                    mod.includeDirs.emplace_back(modDirectoryPath / std::u8string_view((const char8_t*)includeDirU8.c_str()));\n                }\n            }\n\n            if (!foundModSaveFilePath)\n                modSaveFilePathU8 = modIni.getString(\"Main\", \"SaveFile\", std::string());\n        }\n\n        if (!modSaveFilePathU8.empty())\n        {\n            std::replace(modSaveFilePathU8.begin(), modSaveFilePathU8.end(), '\\\\', '/');\n            ModLoader::s_saveFilePath = modDirectoryPath / std::u8string_view((const char8_t*)modSaveFilePathU8.c_str());\n\n            // Save file paths in HMM mods are treated as folders.\n            if (mod.type == ModType::HMM)\n                ModLoader::s_saveFilePath /= \"SYS-DATA\";\n\n            foundModSaveFilePath = true;\n        }\n\n        if (!mod.includeDirs.empty())\n            g_mods.emplace_back(std::move(mod));\n    }\n\n    auto codeCount = modsDbIni.get<size_t>(\"Codes\", \"CodeCount\", 0);\n\n    if (codeCount)\n    {\n        std::vector<std::string> codes{};\n\n        for (size_t i = 0; i < codeCount; i++)\n        {\n            auto name = modsDbIni.getString(\"Codes\", fmt::format(\"Code{}\", i), \"\");\n\n            if (name.empty())\n                continue;\n\n            codes.push_back(name);\n        }\n\n        for (auto& def : g_configDefinitions)\n        {\n            if (!def->IsHidden() || def->GetSection() != \"Codes\")\n                continue;\n\n            /* NOTE: this is inefficient, but it happens\n               once on boot for a handful of codes at release\n               and is temporary until we support real code mods. */\n            for (size_t i = 0; i < codes.size(); i++)\n            {\n                if (def->GetName() == codes[i])\n                {\n                    LOGF_IMPL(Utility, \"Mod Loader\", \"Loading code: \\\"{}\\\"\", codes[i]);\n                    *(bool*)def->GetValue() = true;\n                    break;\n                }\n            }\n        }\n    }\n}\n\nstatic constexpr uint32_t LZX_SIGNATURE = 0xFF512EE;\n\nstatic std::span<uint8_t> decompressLzx(PPCContext& ctx, uint8_t* base, const uint8_t* compressedData, size_t compressedDataSize, be<uint32_t>* scratchSpace)\n{\n    assert(g_memory.IsInMemoryRange(compressedData));\n\n    bool shouldFreeScratchSpace = false;\n    if (scratchSpace == nullptr)\n    {\n        scratchSpace = reinterpret_cast<be<uint32_t>*>(g_userHeap.Alloc(sizeof(uint32_t) * 2));\n        shouldFreeScratchSpace = true;\n    }\n\n    // Initialize decompressor\n    ctx.r3.u32 = 1;\n    ctx.r4.u32 = uint32_t((compressedData + 0xC) - base);\n    ctx.r5.u32 = *reinterpret_cast<const be<uint32_t>*>(compressedData + 0x8);\n    ctx.r6.u32 = uint32_t(reinterpret_cast<uint8_t*>(scratchSpace) - base);\n    // sub_831CE1A0(ctx, base);\n\n    uint64_t decompressedDataSize = *reinterpret_cast<const be<uint64_t>*>(compressedData + 0x18);\n    uint8_t* decompressedData = reinterpret_cast<uint8_t*>(g_userHeap.Alloc(decompressedDataSize));\n\n    uint32_t blockSize = *reinterpret_cast<const be<uint32_t>*>(compressedData + 0x28);\n    size_t decompressedDataOffset = 0;\n    size_t compressedDataOffset = 0x30;\n\n    while (decompressedDataOffset < decompressedDataSize)\n    {\n        size_t decompressedBlockSize = decompressedDataSize - decompressedDataOffset;\n\n        if (decompressedBlockSize > blockSize)\n            decompressedBlockSize = blockSize;\n\n        *(scratchSpace + 1) = decompressedBlockSize;\n\n        uint32_t compressedBlockSize = *reinterpret_cast<const be<uint32_t>*>(compressedData + compressedDataOffset);\n\n        // Decompress\n        ctx.r3.u32 = *scratchSpace;\n        ctx.r4.u32 = uint32_t((decompressedData + decompressedDataOffset) - base);\n        ctx.r5.u32 = uint32_t(reinterpret_cast<uint8_t*>(scratchSpace + 1) - base);\n        ctx.r6.u32 = uint32_t((compressedData + compressedDataOffset + 0x4) - base);\n        ctx.r7.u32 = compressedBlockSize;\n        // sub_831CE0D0(ctx, base);\n\n        decompressedDataOffset += *(scratchSpace + 1);\n        compressedDataOffset += 0x4 + compressedBlockSize;\n    }\n\n    // Deinitialize decompressor\n    ctx.r3.u32 = *scratchSpace;\n    // sub_831CE150(ctx, base);\n\n    if (shouldFreeScratchSpace)\n        g_userHeap.Free(scratchSpace);\n\n    return { decompressedData, decompressedDataSize };\n}\n\n// Hedgehog::Database::CDatabaseLoader::ReadArchiveList\n// PPC_FUNC_IMPL(__imp__sub_82E0D3E8);\n// PPC_FUNC(sub_82E0D3E8)\n// {\n//     if (g_mods.empty())\n//     {\n//         __imp__sub_82E0D3E8(ctx, base);\n//         return;\n//     }\n\n//     thread_local ankerl::unordered_dense::set<std::string> s_fileNames;\n//     s_fileNames.clear();\n\n//     auto parseArlFileData = [&](const uint8_t* arlFileData, size_t arlFileSize)\n//         {\n//             struct ArlHeader\n//             {\n//                 uint32_t signature;\n//                 uint32_t splitCount;\n//             };\n\n//             auto* arlHeader = reinterpret_cast<const ArlHeader*>(arlFileData);\n//             size_t arlHeaderSize = sizeof(ArlHeader) + arlHeader->splitCount * sizeof(uint32_t);\n//             const uint8_t* arlFileNames = arlFileData + arlHeaderSize;\n\n//             while (arlFileNames < arlFileData + arlFileSize)\n//             {\n//                 uint8_t fileNameSize = *arlFileNames;\n//                 ++arlFileNames;\n\n//                 s_fileNames.emplace(reinterpret_cast<const char*>(arlFileNames), fileNameSize);\n\n//                 arlFileNames += fileNameSize;\n//             }\n\n//             return arlHeaderSize;\n//         };\n\n//     auto parseArFileData = [&](const uint8_t* arFileData, size_t arFileSize)\n//         {\n//             struct ArEntry\n//             {\n//                 uint32_t entrySize;\n//                 uint32_t dataSize;\n//                 uint32_t dataOffset;\n//                 uint32_t fileDateLow;\n//                 uint32_t fileDateHigh;\n//             };\n\n//             for (size_t i = 16; i < arFileSize; )\n//             {\n//                 auto entry = reinterpret_cast<const ArEntry*>(arFileData + i);\n//                 s_fileNames.emplace(reinterpret_cast<const char*>(entry + 1));\n//                 i += entry->entrySize;\n//             }\n//         };\n\n//     auto r3 = ctx.r3;\n//     auto r4 = ctx.r4;\n//     auto r5 = ctx.r5;\n//     auto r6 = ctx.r6;\n\n//     auto loadFile = [&]<typename TFunction>(const std::filesystem::path& filePath, const TFunction& function)\n//     {\n//         std::ifstream stream(filePath, std::ios::binary);\n//         if (stream.good())\n//         {\n//             if (ModLoader::s_isLogTypeConsole)\n//                 LOGF_IMPL(Utility, \"Mod Loader\", \"Loading file: \\\"{}\\\"\", reinterpret_cast<const char*>(filePath.u8string().c_str()));\n\n//             be<uint32_t> signature{};\n//             stream.read(reinterpret_cast<char*>(&signature), sizeof(signature));\n\n//             stream.seekg(0, std::ios::end);\n//             size_t arlFileSize = stream.tellg();\n//             stream.seekg(0, std::ios::beg);\n\n//             if (signature == LZX_SIGNATURE)\n//             {\n//                 void* compressedFileData = g_userHeap.Alloc(arlFileSize);\n//                 stream.read(reinterpret_cast<char*>(compressedFileData), arlFileSize);\n//                 stream.close();\n\n//                 auto fileData = decompressLzx(ctx, base, reinterpret_cast<uint8_t*>(compressedFileData), arlFileSize, nullptr);\n\n//                 g_userHeap.Free(compressedFileData);\n\n//                 function(fileData.data(), fileData.size());\n\n//                 g_userHeap.Free(fileData.data());\n//             }\n//             else\n//             {\n//                 thread_local std::vector<uint8_t> s_fileData;\n\n//                 s_fileData.resize(arlFileSize);\n//                 stream.read(reinterpret_cast<char*>(s_fileData.data()), arlFileSize);\n//                 stream.close();\n\n//                 function(s_fileData.data(), arlFileSize);\n//             }\n\n//             return true;\n//         }\n\n//         return false;\n//     };\n\n//     thread_local xxHashMap<std::vector<std::pair<std::filesystem::path, bool>>> s_cache;\n\n//     std::u8string_view arlFilePathU8(reinterpret_cast<const char8_t*>(base + PPC_LOAD_U32(ctx.r4.u32)));\n//     XXH64_hash_t hash = XXH3_64bits(arlFilePathU8.data(), arlFilePathU8.size());\n//     auto findResult = s_cache.find(hash);\n\n//     if (findResult != s_cache.end())\n//     {\n//         for (const auto& [arlFilePath, isArchiveList] : findResult->second)\n//         {\n//             if (isArchiveList)\n//                 loadFile(arlFilePath, parseArlFileData);\n//             else\n//                 loadFile(arlFilePath, parseArFileData);\n//         }\n//     }\n//     else\n//     {\n//         std::vector<std::pair<std::filesystem::path, bool>> arlFilePaths;\n//         std::filesystem::path arlFilePath;\n//         std::filesystem::path arFilePath;\n//         std::filesystem::path appendArlFilePath;\n\n//         for (auto& mod : g_mods)\n//         {\n//             for (auto& includeDir : mod.includeDirs)\n//             {\n//                 auto loadUncachedFile = [&](const std::filesystem::path& filePath, bool isArchiveList)\n//                 {\n//                     if (mod.type == ModType::UMM && mod.readOnly.contains(filePath))\n//                         return false;\n\n//                     std::filesystem::path combinedFilePath = includeDir / filePath;\n\n//                     bool success;\n//                     if (isArchiveList)\n//                         success = loadFile(combinedFilePath, parseArlFileData);\n//                     else\n//                         success = loadFile(combinedFilePath, parseArFileData);\n\n//                     if (success)\n//                         arlFilePaths.emplace_back(std::move(combinedFilePath), isArchiveList);\n\n//                     return success;\n//                 };\n\n//                 if (mod.type == ModType::UMM)\n//                 {\n//                     if (mod.merge)\n//                     {\n//                         if (arlFilePath.empty())\n//                         {\n//                             arlFilePath = arlFilePathU8;\n//                             arlFilePath += \".arl\";\n//                         }\n\n//                         if (!loadUncachedFile(arlFilePath, true))\n//                         {\n//                             if (arFilePath.empty())\n//                             {\n//                                 arFilePath = arlFilePathU8;\n//                                 arFilePath += \".ar\";\n//                             }\n\n//                             if (!loadUncachedFile(arFilePath, false))\n//                             {\n//                                 thread_local std::filesystem::path s_tempPath;\n\n//                                 for (uint32_t i = 0; ; i++)\n//                                 {\n//                                     s_tempPath = arFilePath;\n//                                     s_tempPath += fmt::format(\".{:02}\", i);\n\n//                                     if (!loadUncachedFile(s_tempPath, false))\n//                                         break;\n//                                 }\n//                             }\n//                         }\n//                     }\n//                 }\n//                 else if (mod.type == ModType::HMM)\n//                 {\n//                     if (appendArlFilePath.empty())\n//                     {\n//                         if (arlFilePath.empty())\n//                         {\n//                             arlFilePath = arlFilePathU8;\n//                             arlFilePath += \".arl\";\n//                         }\n\n//                         appendArlFilePath = arlFilePath.parent_path();\n//                         appendArlFilePath /= \"+\";\n//                         appendArlFilePath += arlFilePath.filename();\n//                     }\n\n//                     loadUncachedFile(appendArlFilePath, true);\n//                 }\n//             }\n//         }\n\n//         s_cache.emplace(hash, std::move(arlFilePaths));\n//     }\n\n//     ctx.r3 = r3;\n//     ctx.r4 = r4;\n//     ctx.r5 = r5;\n//     ctx.r6 = r6;\n\n//     if (s_fileNames.empty())\n//     {\n//         __imp__sub_82E0D3E8(ctx, base);\n//         return;\n//     }\n\n//     size_t arlHeaderSize = parseArlFileData(base + ctx.r5.u32, ctx.r6.u32);\n//     size_t arlFileSize = arlHeaderSize;\n\n//     for (auto& fileName : s_fileNames)\n//     {\n//         arlFileSize += 1;\n//         arlFileSize += fileName.size();\n//     }\n\n//     uint8_t* newArlFileData = reinterpret_cast<uint8_t*>(g_userHeap.Alloc(arlFileSize));\n//     memcpy(newArlFileData, base + ctx.r5.u32, arlHeaderSize);\n\n//     uint8_t* arlFileNames = newArlFileData + arlHeaderSize;\n//     for (auto& fileName : s_fileNames)\n//     {\n//         *arlFileNames = uint8_t(fileName.size());\n//         ++arlFileNames;\n//         memcpy(arlFileNames, fileName.data(), fileName.size());\n//         arlFileNames += fileName.size();\n//     }\n\n//     ctx.r5.u32 = uint32_t(newArlFileData - base);\n//     ctx.r6.u32 = uint32_t(arlFileSize);\n\n//     __imp__sub_82E0D3E8(ctx, base);\n\n//     g_userHeap.Free(newArlFileData);\n// }\n\n// Load elements have an unused \"pretty name\" field. We will use this field to store the archive file path,\n// prefixed with a magic string. When the first load detects this string, it will load append archives\n// and then clear the field to prevent remaining splits from loading the append archives again.\n// We cannot rely on .ar.00 being the first split to be loaded, so this approach is necessary.\nstatic thread_local uint32_t g_prefixedArFilePath = NULL;\n\n// Hedgehog::Database::CDatabaseLoader::LoadArchives\n// PPC_FUNC_IMPL(__imp__sub_82E0CC38);\n// PPC_FUNC(sub_82E0CC38)\n// {\n//     if (g_mods.empty())\n//     {\n//         __imp__sub_82E0CC38(ctx, base);\n//         return;\n//     }\n\n//     auto r3 = ctx.r3;\n//     auto r4 = ctx.r4;\n//     auto r5 = ctx.r5;\n//     auto r6 = ctx.r6;\n//     auto r7 = ctx.r7;\n//     auto r8 = ctx.r8;\n\n//     const char* arFilePath = reinterpret_cast<const char*>(base + PPC_LOAD_U32(r5.u32));\n\n//     // __HH_ALLOC\n//     ctx.r3.u32 = 22 + strlen(arFilePath);\n//     sub_822C0988(ctx, base);\n//     char* prefixedArFilePath = reinterpret_cast<char*>(base + ctx.r3.u32);\n\n//     *reinterpret_cast<be<uint32_t>*>(prefixedArFilePath) = 1;\n//     strcpy(prefixedArFilePath + 0x4, \"/UnleashedRecomp/\");\n//     strcpy(prefixedArFilePath + 0x15, arFilePath);\n\n//     ctx.r1.u32 -= 0x10;\n//     uint32_t stackSpace = ctx.r1.u32;\n//     PPC_STORE_U32(stackSpace, static_cast<uint32_t>(reinterpret_cast<uint8_t*>(prefixedArFilePath) - base) + 0x4);\n//     g_prefixedArFilePath = stackSpace;\n\n//     ctx.r3 = r3;\n//     ctx.r4 = r4;\n//     ctx.r5 = r5;\n//     ctx.r6 = r6;\n//     ctx.r7 = r7;\n//     ctx.r8 = r8;\n//     __imp__sub_82E0CC38(ctx, base);\n\n//     // Hedgehog::Base::CSharedString::~CSharedString\n//     ctx.r3.u32 = stackSpace;\n//     sub_82DFB148(ctx, base);\n\n//     g_prefixedArFilePath = NULL;\n//     ctx.r1.u32 += 0x10;\n// }\n\n// Hedgehog::Database::SLoadElement::SLoadElement\n// PPC_FUNC_IMPL(__imp__sub_82E140D8);\n// PPC_FUNC(sub_82E140D8)\n// {\n//     // Store the prefixed archive file path as the pretty name. It's unused for archives we want to append to.\n//     if (!g_mods.empty() && PPC_LOAD_U32(ctx.r5.u32) == 0x8200A621 && g_prefixedArFilePath != NULL)\n//         ctx.r5.u32 = g_prefixedArFilePath;\n\n//     __imp__sub_82E140D8(ctx, base);\n// }\n\n// Hedgehog::Database::CDatabaseLoader::CCreateFromArchive::CreateCallback\n// PPC_FUNC_IMPL(__imp__sub_82E0B500);\n// PPC_FUNC(sub_82E0B500)\n// {\n//     if (g_mods.empty())\n//     {\n//         __imp__sub_82E0B500(ctx, base);\n//         return;\n//     }\n\n//     uint32_t prefixedArFilePath = PPC_LOAD_U32(ctx.r5.u32);\n//     std::u8string_view arFilePathU8(reinterpret_cast<const char8_t*>(base + prefixedArFilePath));\n//     if (!arFilePathU8.starts_with(u8\"/UnleashedRecomp/\"))\n//     {\n//         __imp__sub_82E0B500(ctx, base);\n//         return;\n//     }\n\n//     // Immediately clear the string, so the remaining splits don't load append archives again.\n//     PPC_STORE_U8(prefixedArFilePath, 0x00);\n//     arFilePathU8.remove_prefix(0x11);\n\n//     auto r3 = ctx.r3; // Callback\n//     auto r4 = ctx.r4; // Database\n//     auto r5 = ctx.r5; // Name\n//     auto r6 = ctx.r6; // Data\n//     auto r7 = ctx.r7; // Size\n//     auto r8 = ctx.r8; // Callback data\n\n//     auto loadArchive = [&](const std::filesystem::path& arFilePath)\n//         {\n//             std::ifstream stream(arFilePath, std::ios::binary);\n//             if (stream.good())\n//             {\n//                 if (ModLoader::s_isLogTypeConsole)\n//                     LOGF_IMPL(Utility, \"Mod Loader\", \"Loading file: \\\"{}\\\"\", reinterpret_cast<const char*>(arFilePath.u8string().c_str()));\n\n//                 stream.seekg(0, std::ios::end);\n//                 size_t arFileSize = stream.tellg();\n\n//                 void* arFileData = g_userHeap.Alloc(arFileSize);\n//                 stream.seekg(0, std::ios::beg);\n//                 stream.read(reinterpret_cast<char*>(arFileData), arFileSize);\n//                 stream.close();\n\n//                 auto arFileDataHolder = reinterpret_cast<be<uint32_t>*>(g_userHeap.Alloc(sizeof(uint32_t) * 2));\n\n//                 if (*reinterpret_cast<be<uint32_t>*>(arFileData) == LZX_SIGNATURE)\n//                 {\n//                     auto fileData = decompressLzx(ctx, base, reinterpret_cast<uint8_t*>(arFileData), arFileSize, arFileDataHolder);\n\n//                     g_userHeap.Free(arFileData);\n\n//                     arFileData = fileData.data();\n//                     arFileSize = fileData.size();\n//                 }\n\n//                 arFileDataHolder[0] = g_memory.MapVirtual(arFileData);\n//                 arFileDataHolder[1] = NULL;\n\n//                 ctx.r3 = r3;\n//                 ctx.r4 = r4;\n//                 ctx.r5 = r5;\n//                 ctx.r6.u32 = g_memory.MapVirtual(arFileDataHolder);\n//                 ctx.r7.u32 = uint32_t(arFileSize);\n//                 ctx.r8 = r8;\n\n//                 __imp__sub_82E0B500(ctx, base);\n\n//                 g_userHeap.Free(arFileDataHolder);\n//                 g_userHeap.Free(arFileData);\n\n//                 return true;\n//             }\n\n//             return false;\n//         };\n\n//     thread_local xxHashMap<std::vector<std::filesystem::path>> s_cache;\n\n//     XXH64_hash_t hash = XXH3_64bits(arFilePathU8.data(), arFilePathU8.size());\n//     auto findResult = s_cache.find(hash);\n//     if (findResult != s_cache.end())\n//     {\n//         for (const auto& arFilePath : findResult->second)\n//             loadArchive(arFilePath);\n//     }\n//     else\n//     {\n//         std::vector<std::filesystem::path> arFilePaths;\n//         std::filesystem::path arFilePath;\n//         std::filesystem::path appendArFilePath;\n\n//         for (auto& mod : g_mods)\n//         {\n//             for (auto& includeDir : mod.includeDirs)\n//             {\n//                 auto loadUncachedArchive = [&](const std::filesystem::path& arFilePath)\n//                     {\n//                         if (mod.type == ModType::UMM && mod.readOnly.contains(arFilePath))\n//                             return false;\n\n//                         std::filesystem::path combinedFilePath = includeDir / arFilePath;\n//                         bool success = loadArchive(combinedFilePath);\n//                         if (success)\n//                             arFilePaths.emplace_back(std::move(combinedFilePath));\n\n//                         return success;\n//                     };\n\n//                 auto loadArchives = [&](const std::filesystem::path& arFilePath)\n//                     {\n//                         thread_local std::filesystem::path s_tempPath;\n//                         s_tempPath = arFilePath;\n//                         s_tempPath += \"l\";\n\n//                         if (mod.type == ModType::UMM && mod.readOnly.contains(s_tempPath))\n//                             return;\n\n//                         std::ifstream stream(includeDir / s_tempPath, std::ios::binary);\n//                         if (stream.good())\n//                         {\n//                             be<uint32_t> signature{};\n//                             uint32_t splitCount{};\n//                             stream.read(reinterpret_cast<char*>(&signature), sizeof(signature));\n\n//                             if (signature == LZX_SIGNATURE)\n//                             {\n//                                 stream.seekg(0, std::ios::end);\n//                                 size_t arlFileSize = stream.tellg();\n//                                 stream.seekg(0, std::ios::beg);\n\n//                                 void* compressedFileData = g_userHeap.Alloc(arlFileSize);\n//                                 stream.read(reinterpret_cast<char*>(compressedFileData), arlFileSize);\n//                                 stream.close();\n\n//                                 auto fileData = decompressLzx(ctx, base, reinterpret_cast<uint8_t*>(compressedFileData), arlFileSize, nullptr);\n\n//                                 g_userHeap.Free(compressedFileData);\n\n//                                 splitCount = *reinterpret_cast<uint32_t*>(fileData.data() + 0x4);\n\n//                                 g_userHeap.Free(fileData.data());\n//                             }\n//                             else\n//                             {\n//                                 stream.read(reinterpret_cast<char*>(&splitCount), sizeof(splitCount));\n//                                 stream.close();\n//                             }\n\n//                             if (splitCount == 0)\n//                             {\n//                                 loadUncachedArchive(arFilePath);\n//                             }\n//                             else\n//                             {\n//                                 for (uint32_t i = 0; i < splitCount; i++)\n//                                 {\n//                                     s_tempPath = arFilePath;\n//                                     s_tempPath += fmt::format(\".{:02}\", i);\n//                                     loadUncachedArchive(s_tempPath);\n//                                 }\n//                             }\n//                         }\n//                         else if (mod.type == ModType::UMM)\n//                         {\n//                             if (!loadUncachedArchive(arFilePath))\n//                             {\n//                                 for (uint32_t i = 0; ; i++)\n//                                 {\n//                                     s_tempPath = arFilePath;\n//                                     s_tempPath += fmt::format(\".{:02}\", i);\n//                                     if (!loadUncachedArchive(s_tempPath))\n//                                         break;\n//                                 }\n//                             }\n//                         }\n//                     };\n\n//                 if (mod.type == ModType::UMM)\n//                 {\n//                     if (mod.merge)\n//                     {\n//                         if (arFilePath.empty())\n//                             arFilePath = arFilePathU8;\n\n//                         loadArchives(arFilePath);\n//                     }\n//                 }\n//                 else if (mod.type == ModType::HMM)\n//                 {\n//                     if (appendArFilePath.empty())\n//                     {\n//                         if (arFilePath.empty())\n//                             arFilePath = arFilePathU8;\n\n//                         appendArFilePath = arFilePath.parent_path();\n//                         appendArFilePath /= \"+\";\n//                         appendArFilePath += arFilePath.filename();\n//                     }\n\n//                     loadArchives(appendArFilePath);\n//                 }\n//             }\n//         }\n\n//         s_cache.emplace(hash, std::move(arFilePaths));\n//     }\n\n//     ctx.r3 = r3;\n//     ctx.r4 = r4;\n//     ctx.r5 = r5;\n//     ctx.r6 = r6;\n//     ctx.r7 = r7;\n//     ctx.r8 = r8;\n\n//     __imp__sub_82E0B500(ctx, base);\n// }\n\n// CriAuObjLoc::AttachCueSheet\n// PPC_FUNC_IMPL(__imp__sub_8314A310);\n// PPC_FUNC(sub_8314A310)\n// {\n//     // allocator: 0x4\n//     // capacity: 0x24\n//     // count: 0x28\n//     // data: 0x2C\n//     uint32_t capacity = PPC_LOAD_U32(ctx.r3.u32 + 0x24);\n//     if (capacity == PPC_LOAD_U32(ctx.r3.u32 + 0x28))\n//     {\n//         auto r3 = ctx.r3;\n//         auto r4 = ctx.r4;\n//         auto r5 = ctx.r5;\n\n//         // Allocate\n//         ctx.r3.u32 = PPC_LOAD_U32(r3.u32 + 0x4);\n//         ctx.r4.u32 = (capacity * 2) * sizeof(uint32_t);\n//         ctx.r5.u32 = 0x82195248; // AuObjCueSheet\n//         ctx.r6.u32 = 0x4;\n//         sub_83167FD8(ctx, base);\n\n//         // Copy\n//         uint32_t oldData = PPC_LOAD_U32(r3.u32 + 0x2C);\n//         uint32_t newData = ctx.r3.u32;\n\n//         memcpy(base + newData, base + oldData, capacity * sizeof(uint32_t));\n//         memset(base + newData + (capacity * sizeof(uint32_t)), 0, capacity * sizeof(uint32_t));\n\n//         PPC_STORE_U32(r3.u32 + 0x24, capacity * 2);\n//         PPC_STORE_U32(r3.u32 + 0x2C, newData);\n\n//         // Deallocate\n//         ctx.r3.u32 = PPC_LOAD_U32(r3.u32 + 0x4);\n//         ctx.r4.u32 = oldData;\n//         sub_83168100(ctx, base);\n\n//         ctx.r3 = r3;\n//         ctx.r4 = r4;\n//         ctx.r5 = r5;\n//     }\n\n//     __imp__sub_8314A310(ctx, base);\n// }\n"
  },
  {
    "path": "MarathonRecomp/mod/mod_loader.h",
    "content": "#pragma once\n\nstruct ModLoader\n{\n    static inline bool s_isLogTypeConsole;\n\n    static inline std::filesystem::path s_saveFilePath;\n    \n    static std::filesystem::path ResolvePath(std::string_view path);\n\n    static std::vector<std::filesystem::path>* GetIncludeDirectories(size_t modIndex);\n\n    static void Init();\n};\n"
  },
  {
    "path": "MarathonRecomp/mutex.h",
    "content": "#pragma once\n\n#ifdef _WIN32\n\nstruct Mutex : CRITICAL_SECTION\n{\n    Mutex()\n    {\n        InitializeCriticalSection(this);\n    }\n    ~Mutex()\n    {\n        DeleteCriticalSection(this);\n    }\n\n    void lock()\n    {\n        EnterCriticalSection(this);\n    }\n\n    void unlock()\n    {\n        LeaveCriticalSection(this);\n    }\n};\n\n#else\n\nusing Mutex = std::mutex;\n\n#endif\n"
  },
  {
    "path": "MarathonRecomp/natvis.natvis",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<AutoVisualizer xmlns=\"http://schemas.microsoft.com/vstudio/debugger/natvis/2010\">\n    <Type Name=\"be&lt;*&gt;\">\n        <DisplayString>{get()}</DisplayString>\n        <Expand>\n          <Item Name=\"Value\">get()</Item>\n        </Expand>\n    </Type>\n    <Type Name=\"xpointer&lt;*&gt;\">\n        <DisplayString>{get()}</DisplayString>\n        <Expand>\n          <Item Name=\"Value\">get()</Item>\n        </Expand>\n    </Type>\n    <Type Name=\"boost::shared_ptr&lt;*&gt;\">\n        <DisplayString>{get()}</DisplayString>\n        <Expand>\n          <Item Name=\"Value\">get()</Item>\n        </Expand>\n    </Type>\n</AutoVisualizer>\n"
  },
  {
    "path": "MarathonRecomp/os/.gitignore",
    "content": "![Ww][Ii][Nn]32/"
  },
  {
    "path": "MarathonRecomp/os/linux/logger_linux.cpp",
    "content": "#include <os/logger.h>\n\nvoid os::logger::Init()\n{\n}\n\nvoid os::logger::Log(const std::string_view str, ELogType type, const char* func)\n{\n    if (func)\n    {\n        fmt::println(\"[{}] {}\", func, str);\n    }\n    else\n    {\n        fmt::println(\"{}\", str);\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/os/linux/media_linux.cpp",
    "content": "#include <algorithm>\n#include <atomic>\n#include <optional>\n#include <string>\n#include <thread>\n#include <unordered_map>\n#include <ranges>\n#include <gio/gio.h>\n#include <os/media.h>\n#include <os/logger.h>\n\nenum class PlaybackStatus\n{\n    Stopped,\n    Playing,\n    Paused\n};\n\nstatic const char* DBusInterface = \"org.freedesktop.DBus\";\nstatic const char* DBusPropertiesInterface = \"org.freedesktop.DBus.Properties\";\nstatic const char* DBusPath = \"/org/freedesktop/DBus\";\nstatic const char* MPRIS2Interface = \"org.mpris.MediaPlayer2\";\nstatic const char* MPRIS2PlayerInterface = \"org.mpris.MediaPlayer2.Player\";\nstatic const char* MPRIS2Path = \"/org/mpris/MediaPlayer2\";\n\nstatic std::optional<std::thread> g_dbusThread;\nstatic std::unordered_map<std::string, PlaybackStatus> g_playerStatus;\nstatic std::atomic<bool> g_isPlaying = false;\n\nstatic PlaybackStatus PlaybackStatusFromString(const char* str)\n{\n    if (g_str_equal(str, \"Playing\"))\n        return PlaybackStatus::Playing;\n    else if (g_str_equal(str, \"Paused\"))\n        return PlaybackStatus::Paused;\n    else\n        return PlaybackStatus::Stopped;\n}\n\nstatic void UpdateActiveStatus()\n{\n    g_isPlaying = std::ranges::any_of(\n            g_playerStatus | std::views::values,\n            [](PlaybackStatus status) { return status == PlaybackStatus::Playing; }\n    );\n}\n\nstatic void UpdateActivePlayers(const char* name, PlaybackStatus status)\n{\n    g_playerStatus.insert_or_assign(name, status);\n    UpdateActiveStatus();\n}\n\nstatic PlaybackStatus MPRISGetPlaybackStatus(GDBusConnection* connection, const gchar* name)\n{\n    GError* error;\n    GVariant* response;\n    GVariant* tupleChild;\n    GVariant* value;\n    PlaybackStatus status;\n\n    error = NULL;\n\n    response = g_dbus_connection_call_sync(\n        connection,\n        name,\n        MPRIS2Path,\n        DBusPropertiesInterface,\n        \"Get\",\n        g_variant_new(\"(ss)\", MPRIS2PlayerInterface, \"PlaybackStatus\"),\n        G_VARIANT_TYPE(\"(v)\"),\n        G_DBUS_CALL_FLAGS_NONE,\n        -1,\n        NULL,\n        &error\n    );\n\n    if (!response)\n    {\n        LOGF_ERROR(\"Failed to process D-Bus Get: {}\", error->message);\n        g_clear_error(&error);\n        return PlaybackStatus::Stopped;\n    }\n\n    tupleChild = g_variant_get_child_value(response, 0);\n    value = g_variant_get_variant(tupleChild);\n\n    if (!g_variant_is_of_type(value, G_VARIANT_TYPE_STRING))\n    {\n        LOG_ERROR(\"Failed to process D-Bus Get\");\n        g_variant_unref(tupleChild);\n        return PlaybackStatus::Stopped;\n    }\n\n    status = PlaybackStatusFromString(g_variant_get_string(value, NULL));\n\n    g_variant_unref(value);\n    g_variant_unref(tupleChild);\n    g_variant_unref(response);\n\n    return status;\n}\n\n// Something is very wrong with the system if this happens\nstatic void DBusConnectionClosed(GDBusConnection* connection,\n                                 gboolean remotePeerVanished,\n                                 GError* error,\n                                 gpointer userData)\n{\n    LOG_ERROR(\"D-Bus connection closed\");\n    g_isPlaying = false;\n    g_main_loop_quit((GMainLoop*)userData);\n}\n\nstatic void DBusNameOwnerChanged(GDBusConnection* connection,\n                                 const gchar* senderName,\n                                 const gchar* objectPath,\n                                 const gchar* interfaceName,\n                                 const gchar* signalName,\n                                 GVariant* parameters,\n                                 gpointer userData)\n{\n    const char* name;\n    const char* oldOwner;\n    const char* newOwner;\n\n    g_variant_get(parameters, \"(&s&s&s)\", &name, &oldOwner, &newOwner);\n\n    if (g_str_has_prefix(name, MPRIS2Interface))\n    {\n        if (oldOwner[0])\n        {\n            g_playerStatus.erase(oldOwner);\n        }\n\n        UpdateActiveStatus();\n    }\n}\n\nstatic void MPRISPropertiesChanged(GDBusConnection* connection,\n                                   const gchar* senderName,\n                                   const gchar* objectPath,\n                                   const gchar* interfaceName,\n                                   const gchar* signalName,\n                                   GVariant* parameters,\n                                   gpointer userData)\n{\n    const char* interface;\n    GVariant* changed;\n    GVariantIter iter;\n    const char* key;\n    GVariant* value;\n    PlaybackStatus playbackStatus;\n\n    g_variant_get_child(parameters, 0, \"&s\", &interface);\n    g_variant_get_child(parameters, 1, \"@a{sv}\", &changed);\n\n    g_variant_iter_init(&iter, changed);\n    while (g_variant_iter_next(&iter, \"{&sv}\", &key, &value))\n    {\n        if (g_str_equal(key, \"PlaybackStatus\"))\n        {\n            playbackStatus = PlaybackStatusFromString(g_variant_get_string(value, NULL));\n            UpdateActivePlayers(senderName, playbackStatus);\n            g_variant_unref(value);\n            break;\n        }\n        g_variant_unref(value);\n    }\n\n    g_variant_unref(changed);\n}\n\n/* Called upon CONNECT to discover already active MPRIS2 players by looking for\n   well-known bus names that begin with the MPRIS2 path.\n   g_playerStatus stores unique connection names,\n   not their well-known ones, as the PropertiesChanged signal only provides the\n   former. */\nstatic void DBusListNamesReceived(GObject* object, GAsyncResult* res, gpointer userData)\n{\n    GDBusConnection* connection;\n    GError* error;\n    GVariant* response;\n    GVariant* tupleChild;\n    GVariantIter iter;\n    const gchar* name;\n\n    connection = G_DBUS_CONNECTION(object);\n    error = NULL;\n    response = g_dbus_connection_call_finish(connection, res, &error);\n\n    if (!response)\n    {\n        LOGF_ERROR(\"Failed to process D-Bus ListNames: {}\", error->message);\n        g_clear_error(&error);\n        return;\n    }\n\n    tupleChild = g_variant_get_child_value(response, 0);\n\n    g_variant_iter_init(&iter, tupleChild);\n    while (g_variant_iter_next(&iter, \"&s\", &name))\n    {\n        GVariant* ownerResponse;\n        const gchar* ownerName;\n        PlaybackStatus status;\n\n        if (!g_str_has_prefix(name, MPRIS2Interface))\n            continue;\n\n        ownerResponse = g_dbus_connection_call_sync(\n            connection,\n            DBusInterface,\n            DBusPath,\n            DBusInterface,\n            \"GetNameOwner\",\n            g_variant_new(\"(s)\", name),\n            G_VARIANT_TYPE(\"(s)\"),\n            G_DBUS_CALL_FLAGS_NONE,\n            -1,\n            NULL,\n            &error\n        );\n\n        if (!ownerResponse)\n        {\n            LOGF_ERROR(\"Failed to process D-Bus GetNameOwner: {}\", error->message);\n            g_clear_error(&error);\n            g_variant_unref(tupleChild);\n            g_variant_unref(response);\n            return;\n        }\n\n        g_variant_get(ownerResponse, \"(&s)\", &ownerName);\n        status = MPRISGetPlaybackStatus(connection, ownerName);\n\n        g_playerStatus.insert_or_assign(ownerName, status);\n        g_variant_unref(ownerResponse);\n    }\n\n    UpdateActiveStatus();\n\n    g_variant_unref(tupleChild);\n    g_variant_unref(response);\n}\n\nstatic void DBusThreadProc()\n{\n    GMainContext* mainContext;\n    GMainLoop* mainLoop;\n    GError* error;\n    GDBusConnection* connection;\n\n    mainContext = g_main_context_new();\n    g_main_context_push_thread_default(mainContext);\n    mainLoop = g_main_loop_new(mainContext, FALSE);\n    error = NULL;\n\n    connection = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);\n    if (!connection)\n    {\n        LOGF_ERROR(\"Failed to connect to D-Bus: {}\", error->message);\n        g_clear_error(&error);\n        g_main_context_unref(mainContext);\n        g_main_loop_unref(mainLoop);\n        return;\n    }\n\n    g_dbus_connection_set_exit_on_close(connection, FALSE);\n    g_signal_connect(connection, \"closed\", G_CALLBACK(DBusConnectionClosed), mainLoop);\n\n    // Listen for player connection changes\n    g_dbus_connection_signal_subscribe(\n        connection,\n        DBusInterface,\n        DBusInterface,\n        \"NameOwnerChanged\",\n        DBusPath,\n        NULL,\n        G_DBUS_SIGNAL_FLAGS_NONE,\n        DBusNameOwnerChanged,\n        NULL,\n        NULL\n    );\n\n    // Listen for player status changes\n    g_dbus_connection_signal_subscribe(\n        connection,\n        NULL,\n        DBusPropertiesInterface,\n        \"PropertiesChanged\",\n        MPRIS2Path,\n        NULL,\n        G_DBUS_SIGNAL_FLAGS_NONE,\n        MPRISPropertiesChanged,\n        NULL,\n        NULL\n    );\n\n    // Request list of current players\n    g_dbus_connection_call(\n        connection,\n        DBusInterface,\n        DBusPath,\n        DBusInterface,\n        \"ListNames\",\n        NULL,\n        G_VARIANT_TYPE(\"(as)\"),\n        G_DBUS_CALL_FLAGS_NONE,\n        -1,\n        NULL,\n        DBusListNamesReceived,\n        NULL\n    );\n\n    g_main_loop_run(mainLoop);\n}\n\nbool os::media::IsExternalMediaPlaying()\n{\n    if (!g_dbusThread)\n    {\n        g_dbusThread.emplace(DBusThreadProc);\n        g_dbusThread->detach();\n    }\n\n    return g_isPlaying;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/linux/process_linux.cpp",
    "content": "#include <os/process.h>\n\n#include <signal.h>\n\nstd::filesystem::path os::process::GetExecutablePath()\n{\n    char exePath[PATH_MAX] = {};\n    if (readlink(\"/proc/self/exe\", exePath, PATH_MAX) > 0)\n    {\n        return std::filesystem::path(std::u8string_view((const char8_t*)(exePath)));\n    }\n    else\n    {\n        return std::filesystem::path();\n    }\n}\n\nstd::filesystem::path os::process::GetExecutableRoot()\n{\n    return GetExecutablePath().remove_filename();\n}\n\nstd::filesystem::path os::process::GetWorkingDirectory()\n{\n    char cwd[PATH_MAX] = {};\n    char *res = getcwd(cwd, sizeof(cwd));\n    if (res != nullptr)\n    {\n        return std::filesystem::path(std::u8string_view((const char8_t*)(cwd)));\n    }\n    else\n    {\n        return std::filesystem::path();\n    }\n}\n\nbool os::process::SetWorkingDirectory(const std::filesystem::path& path)\n{\n    return chdir(path.c_str()) == 0;\n}\n\nbool os::process::StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work)\n{\n    pid_t pid = fork();\n    if (pid < 0)\n        return false;\n\n    if (pid == 0)\n    {\n        setsid();\n        \n        std::u8string workU8 = work.u8string();\n        chdir((const char*)(workU8.c_str())); \n        \n        std::u8string pathU8 = path.u8string();\n        std::vector<char*> argStrs;\n        argStrs.push_back((char*)(pathU8.c_str()));\n        for (const std::string& arg : args)\n            argStrs.push_back((char *)(arg.c_str()));\n        \n        argStrs.push_back(nullptr);\n        execvp((const char*)(pathU8.c_str()), argStrs.data());\n        raise(SIGKILL);\n    }\n\n    return true;\n}\n\nvoid os::process::CheckConsole()\n{\n    // Always visible on Linux.\n    g_consoleVisible = true;\n}\n\nvoid os::process::ShowConsole()\n{\n    // Unnecessary on Linux.\n}\n"
  },
  {
    "path": "MarathonRecomp/os/linux/registry_linux.inl",
    "content": "#include <os/registry.h>\n\n// TODO: Implement\ninline bool os::registry::Init()\n{\n    return false;\n}\n\n// TODO: read from file?\ntemplate<typename T>\nbool os::registry::ReadValue(const std::string_view& name, T& data)\n{\n    return false;\n}\n\n// TODO: write to file?\ntemplate<typename T>\nbool os::registry::WriteValue(const std::string_view& name, const T& data)\n{\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/linux/user_linux.cpp",
    "content": "#include <os/user.h>\n\nbool os::user::IsDarkTheme()\n{\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/linux/version_linux.cpp",
    "content": "#include <os/version.h>\n\nos::version::OSVersion os::version::GetOSVersion()\n{\n    assert(false && \"Unimplemented.\");\n    return os::version::OSVersion();\n}\n"
  },
  {
    "path": "MarathonRecomp/os/logger.h",
    "content": "#pragma once\n\n#include <source_location>\n\n#define LOG_IMPL(type, func, str)       os::logger::Log(str, os::logger::ELogType::type, func)\n#define LOGF_IMPL(type, func, str, ...) os::logger::Log(fmt::format(str, __VA_ARGS__), os::logger::ELogType::type, func)\n\n// Function-specific logging.\n\n#define LOG(str)               LOG_IMPL(None, __func__, str)\n#define LOG_WARNING(str)       LOG_IMPL(Warning, __func__, str)\n#define LOG_ERROR(str)         LOG_IMPL(Error, __func__, str)\n\n#if _DEBUG\n#define LOG_UTILITY(str)       LOG_IMPL(Utility, __func__, str)\n#else\n#define LOG_UTILITY(str)       LOG_IMPL(Utility, __func__, str)\n#endif\n\n#define LOGF(str, ...)         LOGF_IMPL(None, __func__, str, __VA_ARGS__)\n#define LOGF_WARNING(str, ...) LOGF_IMPL(Warning, __func__, str, __VA_ARGS__)\n#define LOGF_ERROR(str, ...)   LOGF_IMPL(Error, __func__, str, __VA_ARGS__)\n\n#if _DEBUG\n#define LOGF_UTILITY(str, ...) LOGF_IMPL(Utility, __func__, str, __VA_ARGS__)\n#else\n#define LOGF_UTILITY(str, ...) LOGF_IMPL(Utility, __func__, str, __VA_ARGS__)\n#endif\n\n// Non-function-specific logging.\n\n#define LOGN(str)               LOG_IMPL(None, \"*\", str)\n#define LOGN_WARNING(str)       LOG_IMPL(Warning, \"*\", str)\n#define LOGN_ERROR(str)         LOG_IMPL(Error, \"*\", str)\n\n#if _DEBUG\n#define LOGN_UTILITY(str)       LOG_IMPL(Utility, \"*\", str)\n#else\n#define LOGN_UTILITY(str)       LOG_IMPL(Utility, \"*\", str)\n#endif\n\n#define LOGFN(str, ...)         LOGF_IMPL(None, \"*\", str, __VA_ARGS__)\n#define LOGFN_WARNING(str, ...) LOGF_IMPL(Warning, \"*\", str, __VA_ARGS__)\n#define LOGFN_ERROR(str, ...)   LOGF_IMPL(Error, \"*\", str, __VA_ARGS__)\n\n#if _DEBUG\n#define LOGFN_UTILITY(str, ...) LOGF_IMPL(Utility, \"*\", str, __VA_ARGS__)\n#else\n#define LOGFN_UTILITY(str, ...) LOGF_IMPL(Utility, \"*\", str, __VA_ARGS__)\n#endif\n\nnamespace os::logger\n{\n    enum class ELogType\n    {\n        None,\n        Utility,\n        Warning,\n        Error\n    };\n\n    void Init();\n    void Log(const std::string_view str, ELogType type = ELogType::None, const char* func = nullptr);\n}\n"
  },
  {
    "path": "MarathonRecomp/os/macos/logger_macos.cpp",
    "content": "#include <os/logger.h>\n\nvoid os::logger::Init()\n{\n}\n\nvoid os::logger::Log(const std::string_view str, ELogType type, const char* func)\n{\n    if (func)\n    {\n        fmt::println(\"[{}] {}\", func, str);\n    }\n    else\n    {\n        fmt::println(\"{}\", str);\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/os/macos/media_macos.cpp",
    "content": "#include <os/media.h>\n\nbool os::media::IsExternalMediaPlaying()\n{\n    // This functionality is not supported in macOS.\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/macos/process_macos.cpp",
    "content": "#include <os/process.h>\n\n#include <CoreFoundation/CFBundle.h>\n#include <dlfcn.h>\n#include <mach-o/dyld.h>\n#include <signal.h>\n#include <sys/param.h>\n#include <unistd.h>\n\nstd::filesystem::path os::process::GetExecutablePath()\n{\n    uint32_t exePathSize = PATH_MAX;\n    char exePath[PATH_MAX] = {};\n    if (_NSGetExecutablePath(exePath, &exePathSize) == 0)\n    {\n        return std::filesystem::path(std::u8string_view((const char8_t*)(exePath)));\n    }\n    else\n    {\n        return std::filesystem::path();\n    }\n}\n\nstd::filesystem::path os::process::GetExecutableRoot()\n{\n    std::filesystem::path resultPath = GetExecutablePath().remove_filename();\n    if (CFBundleRef bundleRef = CFBundleGetMainBundle())\n    {\n        if (CFURLRef bundleUrlRef = CFBundleCopyBundleURL(bundleRef))\n        {\n            char appBundlePath[MAXPATHLEN];\n            if (CFURLGetFileSystemRepresentation(bundleUrlRef, true, (uint8_t*)(appBundlePath), sizeof(appBundlePath)))\n            {\n                resultPath = std::filesystem::path(appBundlePath).parent_path();\n            }\n            CFRelease(bundleUrlRef);\n        }\n    }\n    return resultPath;\n}\n\nstd::filesystem::path os::process::GetWorkingDirectory()\n{\n    char cwd[PATH_MAX] = {};\n    char *res = getcwd(cwd, sizeof(cwd));\n    if (res != nullptr)\n    {\n        return std::filesystem::path(std::u8string_view((const char8_t*)(cwd)));\n    }\n    else\n    {\n        return std::filesystem::path();\n    }\n}\n\nbool os::process::SetWorkingDirectory(const std::filesystem::path& path)\n{\n    return chdir(path.c_str()) == 0;\n}\n\nbool os::process::StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work)\n{\n    pid_t pid = fork();\n    if (pid < 0)\n        return false;\n\n    if (pid == 0)\n    {\n        setsid();\n\n        std::u8string workU8 = work.u8string();\n        chdir((const char*)(workU8.c_str()));\n\n        std::u8string pathU8 = path.u8string();\n        std::vector<char*> argStrs;\n        argStrs.push_back((char*)(pathU8.c_str()));\n        for (const std::string& arg : args)\n            argStrs.push_back((char *)(arg.c_str()));\n\n        argStrs.push_back(nullptr);\n        execvp((const char*)(pathU8.c_str()), argStrs.data());\n        raise(SIGKILL);\n    }\n\n    return true;\n}\n\nvoid os::process::CheckConsole()\n{\n    // Always visible on macOS.\n    g_consoleVisible = true;\n}\n\nvoid os::process::ShowConsole()\n{\n    // Unnecessary on macOS.\n}\n"
  },
  {
    "path": "MarathonRecomp/os/macos/registry_macos.inl",
    "content": "#include <os/registry.h>\n\n// TODO: Implement\ninline bool os::registry::Init()\n{\n    return false;\n}\n\n// TODO: read from file?\ntemplate<typename T>\nbool os::registry::ReadValue(const std::string_view& name, T& data)\n{\n    return false;\n}\n\n// TODO: write to file?\ntemplate<typename T>\nbool os::registry::WriteValue(const std::string_view& name, const T& data)\n{\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/macos/user_macos.cpp",
    "content": "#include <os/user.h>\n\nbool os::user::IsDarkTheme()\n{\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/macos/version_macos.cpp",
    "content": "#include <os/version.h>\n\nos::version::OSVersion os::version::GetOSVersion()\n{\n    assert(false && \"Unimplemented.\");\n    return os::version::OSVersion();\n}\n"
  },
  {
    "path": "MarathonRecomp/os/media.h",
    "content": "#pragma once\n\nnamespace os::media\n{\n    bool IsExternalMediaPlaying();\n}\n"
  },
  {
    "path": "MarathonRecomp/os/process.h",
    "content": "#pragma once\n\nnamespace os::process\n{\n    inline bool g_consoleVisible;\n\n    std::filesystem::path GetExecutablePath();\n    std::filesystem::path GetExecutableRoot();\n    std::filesystem::path GetWorkingDirectory();\n    bool SetWorkingDirectory(const std::filesystem::path& path);\n    bool StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work = {});\n    void CheckConsole();\n    void ShowConsole();\n}\n"
  },
  {
    "path": "MarathonRecomp/os/registry.h",
    "content": "#pragma once\n\nnamespace os::registry\n{\n    bool Init();\n\n    template<typename T>\n    bool ReadValue(const std::string_view& name, T& data);\n\n    template<typename T>\n    bool WriteValue(const std::string_view& name, const T& data);\n}\n\n#if _WIN32\n#include <os/win32/registry_win32.inl>\n#elif defined(__linux__)\n#include <os/linux/registry_linux.inl>\n#elif defined(__APPLE__)\n#include <os/macos/registry_macos.inl>\n#endif\n"
  },
  {
    "path": "MarathonRecomp/os/user.h",
    "content": "#pragma once\n\nnamespace os::user\n{\n    bool IsDarkTheme();\n}\n"
  },
  {
    "path": "MarathonRecomp/os/version.h",
    "content": "#pragma once\n\nnamespace os::version\n{\n    struct OSVersion\n    {\n        uint32_t Major{};\n        uint32_t Minor{};\n        uint32_t Build{};\n    };\n\n    OSVersion GetOSVersion();\n}\n"
  },
  {
    "path": "MarathonRecomp/os/win32/logger_win32.cpp",
    "content": "#include <os/logger.h>\n#include <os/process.h>\n\n#define FOREGROUND_WHITE  (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)\n#define FOREGROUND_YELLOW (FOREGROUND_RED | FOREGROUND_GREEN)\n\nstatic HANDLE g_hStandardOutput;\n\nvoid os::logger::Init()\n{\n    g_hStandardOutput = GetStdHandle(STD_OUTPUT_HANDLE);\n}\n\nvoid os::logger::Log(const std::string_view str, ELogType type, const char* func)\n{\n    if (!os::process::g_consoleVisible)\n        return;\n\n    switch (type)\n    {\n        case ELogType::Utility:\n            SetConsoleTextAttribute(g_hStandardOutput, FOREGROUND_GREEN | FOREGROUND_INTENSITY);\n            break;\n\n        case ELogType::Warning:\n            SetConsoleTextAttribute(g_hStandardOutput, FOREGROUND_YELLOW | FOREGROUND_INTENSITY);\n            break;\n\n        case ELogType::Error:\n            SetConsoleTextAttribute(g_hStandardOutput, FOREGROUND_RED | FOREGROUND_INTENSITY);\n            break;\n\n        default:\n            SetConsoleTextAttribute(g_hStandardOutput, FOREGROUND_WHITE);\n            break;\n    }\n\n    if (func)\n    {\n        fmt::println(\"[{}] {}\", func, str);\n    }\n    else\n    {\n        fmt::println(\"{}\", str);\n    }\n\n    SetConsoleTextAttribute(g_hStandardOutput, FOREGROUND_WHITE);\n}\n"
  },
  {
    "path": "MarathonRecomp/os/win32/media_win32.cpp",
    "content": "#include <os/media.h>\n#include <os/logger.h>\n#include <winrt/Windows.Foundation.h>\n#include <winrt/Windows.Media.Control.h>\n\nusing namespace winrt;\nusing namespace winrt::Windows::Foundation;\nusing namespace winrt::Windows::Media::Control;\n\nstatic GlobalSystemMediaTransportControlsSessionManager g_sessionManager = nullptr;\n\nstatic GlobalSystemMediaTransportControlsSessionManager GetSessionManager()\n{\n    if (g_sessionManager)\n        return g_sessionManager;\n\n    try\n    {\n        init_apartment();\n        return g_sessionManager = GlobalSystemMediaTransportControlsSessionManager::RequestAsync().get();\n    }\n    catch (...)\n    {\n        LOGF_ERROR(\"Failed to retrieve GSMTC session manager: 0x{:X}\", to_hresult().value);\n        return nullptr;\n    }\n}\n\nstatic GlobalSystemMediaTransportControlsSession GetCurrentSession()\n{\n    auto sessionManager = GetSessionManager();\n\n    if (!sessionManager)\n        return nullptr;\n\n    try\n    {\n        return sessionManager.GetCurrentSession();\n    }\n    catch (...)\n    {\n        LOGF_ERROR(\"Failed to retrieve current GSMTC session: 0x{:X}\", to_hresult().value);\n        return nullptr;\n    }\n}\n\nstatic GlobalSystemMediaTransportControlsSessionPlaybackInfo GetPlaybackInfo()\n{\n    auto session = GetCurrentSession();\n\n    if (!session)\n        return nullptr;\n\n    try\n    {\n        return session.GetPlaybackInfo();\n    }\n    catch (...)\n    {\n        LOGF_ERROR(\"Failed to retrieve GSMTC playback info: 0x{:X}\", to_hresult().value);\n        return nullptr;\n    }\n}\n\nbool os::media::IsExternalMediaPlaying()\n{\n    auto playbackInfo = GetPlaybackInfo();\n\n    if (!playbackInfo)\n        return false;\n\n    try\n    {\n        return playbackInfo.PlaybackStatus() == GlobalSystemMediaTransportControlsSessionPlaybackStatus::Playing;\n    }\n    catch (...)\n    {\n        LOGF_ERROR(\"Failed to retrieve GSMTC playback status: 0x{:X}\", to_hresult().value);\n        return false;\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/os/win32/process_win32.cpp",
    "content": "#include <os/process.h>\n\nstd::filesystem::path os::process::GetExecutablePath()\n{\n    WCHAR exePath[MAX_PATH];\n\n    if (!GetModuleFileNameW(nullptr, exePath, MAX_PATH))\n        return std::filesystem::path();\n\n    return std::filesystem::path(exePath);\n}\n\nstd::filesystem::path os::process::GetExecutableRoot()\n{\n    return GetExecutablePath().remove_filename();\n}\n\nstd::filesystem::path os::process::GetWorkingDirectory()\n{\n    WCHAR workPath[MAX_PATH];\n\n    if (!GetCurrentDirectoryW(MAX_PATH, workPath))\n        return std::filesystem::path();\n\n    return std::filesystem::path(workPath);\n}\n\nbool os::process::SetWorkingDirectory(const std::filesystem::path& path)\n{\n    return SetCurrentDirectoryW(path.c_str());\n}\n\nbool os::process::StartProcess(const std::filesystem::path& path, const std::vector<std::string>& args, std::filesystem::path work)\n{\n    if (path.empty())\n        return false;\n\n    if (work.empty())\n        work = path.parent_path();\n\n    auto cli = path.wstring();\n\n    // NOTE: We assume the CLI arguments only contain ASCII characters.\n    for (auto& arg : args)\n        cli += L\" \" + std::wstring(arg.begin(), arg.end());\n\n    STARTUPINFOW startInfo{ sizeof(STARTUPINFOW) };\n    PROCESS_INFORMATION procInfo{};\n    std::wstring pathW = path.wstring();\n    std::wstring workW = work.wstring();\n    if (!CreateProcessW(pathW.c_str(), cli.data(), nullptr, nullptr, false, 0, nullptr, workW.c_str(), &startInfo, &procInfo))\n        return false;\n\n    CloseHandle(procInfo.hProcess);\n    CloseHandle(procInfo.hThread);\n\n    return true;\n}\n\nvoid os::process::CheckConsole()\n{\n    g_consoleVisible = (GetConsoleWindow() != nullptr);\n}\n\nvoid os::process::ShowConsole()\n{\n    if (GetConsoleWindow() == nullptr)\n    {\n        AllocConsole();\n        freopen(\"CONIN$\", \"r\", stdin);\n        freopen(\"CONOUT$\", \"w\", stderr);\n        freopen(\"CONOUT$\", \"w\", stdout);\n\n        g_consoleVisible = true;\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/os/win32/registry_win32.inl",
    "content": "#include <os/registry.h>\n\ninline const wchar_t* g_registryRoot = L\"Software\\\\MarathonRecomp\";\n\ninline bool os::registry::Init()\n{\n    return true;\n}\n\ntemplate<typename T>\nbool os::registry::ReadValue(const std::string_view& name, T& data)\n{\n    HKEY hKey;\n\n    if (RegOpenKeyExW(HKEY_CURRENT_USER, g_registryRoot, 0, KEY_READ, &hKey) != ERROR_SUCCESS)\n        return false;\n\n    wchar_t wideName[128];\n    int wideNameSize = MultiByteToWideChar(CP_UTF8, 0, name.data(), name.size(), wideName, sizeof(wideName));\n    if (wideNameSize == 0)\n    {\n        return false;\n    }\n\n    wideName[wideNameSize] = 0;\n    DWORD bufferSize = 0;\n    DWORD dataType = 0;\n\n    auto result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_ANY, &dataType, nullptr, &bufferSize);\n\n    if (result != ERROR_SUCCESS)\n    {\n        RegCloseKey(hKey);\n        return false;\n    }\n\n    result = ERROR_INVALID_FUNCTION;\n    if constexpr (std::is_same_v<T, std::string>)\n    {\n        if (dataType == REG_SZ)\n        {\n            std::vector<uint8_t> buffer{};\n            buffer.reserve(bufferSize);\n            result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_REG_SZ, nullptr, buffer.data(), &bufferSize);\n\n            if (result == ERROR_SUCCESS)\n            {\n                int valueSize = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)buffer.data(), (bufferSize / sizeof(wchar_t)) - 1, nullptr, 0, nullptr, nullptr);\n                data.resize(valueSize);\n                WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)buffer.data(), (bufferSize / sizeof(wchar_t)) - 1, data.data(), valueSize, nullptr, nullptr);\n            }\n        }\n    }\n    else if constexpr (std::is_same_v<T, std::filesystem::path>)\n    {\n        if (dataType == REG_SZ)\n        {\n            std::vector<uint8_t> buffer{};\n            buffer.reserve(bufferSize);\n            result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_REG_SZ, nullptr, buffer.data(), &bufferSize);\n\n            if (result == ERROR_SUCCESS)\n            {\n                data = reinterpret_cast<wchar_t*>(buffer.data());\n            }\n        }\n    }\n    else if constexpr (std::is_same_v<T, uint32_t>)\n    {\n        result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_DWORD, nullptr, (BYTE*)&data, &bufferSize);\n    }\n    else if constexpr (std::is_same_v<T, uint64_t>)\n    {\n        result = RegGetValueW(hKey, nullptr, wideName, RRF_RT_QWORD, nullptr, (BYTE*)&data, &bufferSize);\n    }\n    else\n    {\n        static_assert(false, \"Unsupported data type.\");\n    }\n\n    RegCloseKey(hKey);\n    return result == ERROR_SUCCESS;\n}\n\ntemplate<typename T>\nbool os::registry::WriteValue(const std::string_view& name, const T& data)\n{\n    HKEY hKey;\n\n    if (RegCreateKeyExW(HKEY_CURRENT_USER, g_registryRoot, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)\n        return false;\n\n    BYTE* pData = nullptr;\n    DWORD dataSize = 0;\n    DWORD dataType = 0;\n    bool wideString = false;\n\n    if constexpr (std::is_same_v<T, std::string>)\n    {\n        pData = (BYTE*)data.c_str();\n        dataSize = data.size() + 1;\n        dataType = REG_SZ;\n    }\n    else if constexpr (std::is_same_v<T, uint32_t>)\n    {\n        pData = &data;\n        dataSize = sizeof(T);\n        dataType = REG_DWORD;\n    }\n    else if constexpr (std::is_same_v<T, uint64_t>)\n    {\n        pData = &data;\n        dataSize = sizeof(T);\n        dataType = REG_QWORD;\n    }\n    else if constexpr (std::is_same_v<T, std::filesystem::path>)\n    {\n        pData = (BYTE*)data.c_str();\n        dataSize = (wcslen((const wchar_t*)pData) + 1) * sizeof(wchar_t);\n        dataType = REG_SZ;\n        wideString = true;\n    }\n    else\n    {\n        static_assert(false, \"Unsupported data type.\");\n    }\n\n    LSTATUS result = ERROR_INVALID_FUNCTION;\n    if (wideString)\n    {\n        wchar_t wideName[128];\n        int wideNameSize = MultiByteToWideChar(CP_UTF8, 0, name.data(), name.size(), wideName, sizeof(wideName));\n        if (wideNameSize == 0)\n        {\n            return false;\n        }\n\n        wideName[wideNameSize] = 0;\n        result = RegSetValueExW(hKey, wideName, 0, dataType, pData, dataSize);\n    }\n    else\n    {\n        result = RegSetValueExA(hKey, name.data(), 0, dataType, pData, dataSize);\n    }\n\n    RegCloseKey(hKey);\n\n    if (result != ERROR_SUCCESS)\n        return false;\n\n    return true;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/win32/user_win32.cpp",
    "content": "#include <os/user.h>\n\nbool os::user::IsDarkTheme()\n{\n    HKEY hKey;\n\n    if (RegOpenKeyExA(HKEY_CURRENT_USER, \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Themes\\\\Personalize\", 0, KEY_READ, &hKey) == ERROR_SUCCESS)\n    {\n        DWORD value = 0;\n        DWORD valueSize = sizeof(value);\n\n        if (RegQueryValueExA(hKey, \"AppsUseLightTheme\", nullptr, nullptr, (LPBYTE)&value, &valueSize) == ERROR_SUCCESS)\n        {\n            RegCloseKey(hKey);\n\n            return value == 0;\n        }\n\n        RegCloseKey(hKey);\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/os/win32/version_win32.cpp",
    "content": "#include <os/version.h>\n\nLIB_FUNCTION(LONG, \"ntdll.dll\", RtlGetVersion, PRTL_OSVERSIONINFOW);\n\nos::version::OSVersion os::version::GetOSVersion()\n{\n    auto result = os::version::OSVersion{};\n\n    OSVERSIONINFOEXW osvi = { 0 };\n    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);\n\n    if (RtlGetVersion((PRTL_OSVERSIONINFOW)&osvi) != 0)\n        return result;\n\n    result.Major = osvi.dwMajorVersion;\n    result.Minor = osvi.dwMinorVersion;\n    result.Build = osvi.dwBuildNumber;\n\n    return result;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/MainMenuTask_patches.cpp",
    "content": "#include \"MainMenuTask_patches.h\"\n#include <ui/achievement_menu.h>\n#include <ui/button_window.h>\n#include <ui/options_menu.h>\n#include <exports.h>\n\n// Sonicteam::MainMenuTask::Update\nPPC_FUNC_IMPL(__imp__sub_824FFCF8);\nPPC_FUNC(sub_824FFCF8)\n{\n    auto pMainMenuTask = (Sonicteam::MainMenuTask*)(base + ctx.r3.u32);\n    auto pHUDMainMenu = pMainMenuTask->m_pHUDMainMenu;\n\n#ifdef MARATHON_RECOMP_OPTIONS_MENU\n    if (pHUDMainMenu)\n    {\n        // Hide original \"OPTIONS\" title.\n        if (auto pOptionsSelect = pHUDMainMenu->m_pHudTextRoot->Find(\"options_select\", \"options\"))\n            pOptionsSelect->m_Priority = -1.0f;\n    }\n\n    if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_MainMenu && pMainMenuTask->m_MainMenuSelectedIndex == 3)\n    {\n        if (!OptionsMenu::s_isVisible && (pMainMenuTask->m_PressedButtons.get() & 0x10) != 0)\n        {\n            OptionsMenu::s_pMainMenuTask = pMainMenuTask;\n            OptionsMenu::Open();\n    \n            Game_PlaySound(\"main_deside\");\n    \n            pMainMenuTask->m_State = Sonicteam::MainMenuTask::MainMenuState_MainMenu;\n            pMainMenuTask->m_PressedButtons = 0;\n\n            guest_stack_var<Sonicteam::Message::HUDMainMenu::MsgSetCursor> msgSetCursor\n            (\n                Sonicteam::HUDMainMenu::HUDMainMenuState_MainCursorOutro,\n                pMainMenuTask->m_MainMenuSelectedIndex\n            );\n\n            // Play cursor outro animation.\n            pHUDMainMenu->ProcessMessage(msgSetCursor.get());\n\n            guest_stack_var<Sonicteam::Message::HUDMainMenu::MsgTransition> msgTransition\n            (\n                Sonicteam::HUDMainMenu::HUDMainMenuState_OptionsOutro, 3\n            );\n\n            // Play main menu -> options transition.\n            pHUDMainMenu->ProcessMessage(msgTransition.get());\n        }\n    }\n\n    static bool s_isReturningFromOptionsMenu{};\n\n    if (OptionsMenu::s_isVisible)\n    {\n        pMainMenuTask->m_PressedButtons = 0;\n\n        if (OptionsMenu::s_state == OptionsMenuState::Closing)\n            s_isReturningFromOptionsMenu = true;\n    }\n    else if (s_isReturningFromOptionsMenu)\n    {\n        if ((pHUDMainMenu->m_CursorFlags.get() & 2) != 0)\n        {\n            // Prevent inputs leaking from the\n            // options menu to the main menu.\n            pMainMenuTask->m_PressedButtons = 0;\n        }\n        else\n        {\n            s_isReturningFromOptionsMenu = false;\n        }\n    }\n#endif\n\n#ifdef MARATHON_RECOMP_ACHIEVEMENT_MENU\n    if (pHUDMainMenu)\n    {\n        // Hide original \"GOLD MEDAL RESULTS\" title.\n        if (auto pGoldMedalResultSelect = pHUDMainMenu->m_pHudTextRoot->Find(\"goldmedalresult_select\", \"goldmedalresult\"))\n            pGoldMedalResultSelect->m_Priority = -1.0f;\n    }\n\n    if (pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResultsOpen)\n        AchievementMenu::Open(pMainMenuTask);\n#endif\n\n    static float s_buttonWindowTextOffsetY{};\n    auto& rButtonWindowTextOffsetY = pMainMenuTask->m_pButtonWindowTask->m_pHUDButtonWindow->m_pHudTextParts->m_OffsetY;\n\n    if (MainMenuTaskPatches::s_hideButtonWindow)\n    {\n        static constexpr double HIDE_TEXT_OFFSET = -100000.0f;\n\n        if (rButtonWindowTextOffsetY != HIDE_TEXT_OFFSET)\n        {\n            // Move original button window text very far off screen to hide it.\n            s_buttonWindowTextOffsetY = rButtonWindowTextOffsetY;\n            rButtonWindowTextOffsetY = HIDE_TEXT_OFFSET;\n        }\n    }\n    else\n    {\n        // Restore original button window text offset.\n        rButtonWindowTextOffsetY = s_buttonWindowTextOffsetY;\n    }\n\n    MainMenuTaskPatches::s_state = (Sonicteam::MainMenuTask::MainMenuState)pMainMenuTask->m_State.get();\n\n    for (auto& event : MainMenuTaskPatches::s_events)\n        event->Update(pMainMenuTask, ctx.f1.f64);\n\n    __imp__sub_824FFCF8(ctx, base);\n}\n\nbool HUDGoldMedal_ShouldDestroyTable()\n{\n    return AchievementMenu::s_state == AchievementMenuState::ClosingGoldMedals ||\n           AchievementMenu::s_state == AchievementMenuState::ClosingAchievements;\n}\n\nbool MainMenuTask_GoldMedalResults_SkipOutro()\n{\n    return AchievementMenu::s_state == AchievementMenuState::ClosingAchievements;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/MainMenuTask_patches.h",
    "content": "#pragma once\n\n#include <api/Marathon.h>\n#include <patches/hook_event.h>\n\nclass MainMenuTaskPatches\n{\npublic:\n    static inline bool s_hideButtonWindow{};\n    static inline Sonicteam::MainMenuTask::MainMenuState s_state{};\n    static inline std::vector<IContextHookEvent<Sonicteam::MainMenuTask>*> s_events{};\n};\n"
  },
  {
    "path": "MarathonRecomp/patches/SaveDataTask_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <ui/message_window.h>\n#include <user/achievement_manager.h>\n#include <user/config.h>\n\nenum\n{\n    ACH_ERROR_SUCCESS,\n    ACH_ERROR_READ,\n    ACH_ERROR_WRITE\n};\n\nstatic Sonicteam::SaveDataTaskXENON::SaveDataOperation g_currentAlert{};\nstatic int g_achError{ ACH_ERROR_SUCCESS };\nstatic int g_achErrorIgnored{ ACH_ERROR_SUCCESS };\n\n// Sonicteam::SaveDataTaskXENON::RunOperation (speculatory)\nPPC_FUNC_IMPL(__imp__sub_8238CB18);\nPPC_FUNC(sub_8238CB18)\n{\n    // Redirect storage device lost alert to load failed alert.\n    // The user should never see this alert, but if they do, it should make a bit more sense.\n    if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_AlertSelectDevice)\n        ctx.r4.u32 = Sonicteam::SaveDataTaskXENON::SaveDataOperation_AlertLoadFailed;\n\n    // Update current alert to operations that only display\n    // messages so we can keep track of the last message displayed.\n    if (ctx.r4.u32 < 3 && ctx.r4.u32 >= 6 && ctx.r4.u32 < 8)\n        g_currentAlert = (Sonicteam::SaveDataTaskXENON::SaveDataOperation)ctx.r4.u32;\n\n    // Redirect overwrite alert to just save the game, if requested.\n    if (Config::Autosave && ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_AlertOverwrite)\n        ctx.r4.u32 = Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData;\n\n    // Redirect storage device select operation to access the save data.\n    // This option is replaced with a \"Retry\" option, so it should attempt to access it again.\n    if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_SelectStorageDevice)\n    {\n        if (g_currentAlert == Sonicteam::SaveDataTaskXENON::SaveDataOperation_AlertLoadFailed)\n        {\n            ctx.r4.u32 = Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData;\n        }\n        else\n        {\n            ctx.r4.u32 = Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData;\n        }\n    }\n\n    // Attempt to load achievement data on save read.\n    if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData)\n    {\n        if (!AchievementManager::LoadBinary())\n        {\n            // This condition will always be met if the achievement data is corrupted (!= IOError),\n            // or as long as \"Continue without saving.\" has not been chosen on load in this session.\n            if (AchievementManager::BinStatus != EAchBinStatus::IOError || g_achErrorIgnored != ACH_ERROR_READ)\n            {\n                g_achError = ACH_ERROR_READ;\n                return;\n            }\n        }\n    }\n\n    // Attempt to save achievement data on save write.\n    if (ctx.r4.u32 == Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData)\n    {\n        // This condition will be met if the achievement data failed to save (any error status),\n        // and as long as \"Continue without saving.\" has not been chosen on save in this session.\n        if (!AchievementManager::SaveBinary() && g_achErrorIgnored != ACH_ERROR_WRITE)\n        {\n            g_achError = ACH_ERROR_WRITE;\n            return;\n        }\n    }\n\n    __imp__sub_8238CB18(ctx, base);\n}\n\n// Sonicteam::SaveDataTaskXENON::Update\nPPC_FUNC_IMPL(__imp__sub_8238D5C8);\nPPC_FUNC(sub_8238D5C8)\n{\n    auto pSaveDataTaskXENON = (Sonicteam::SaveDataTaskXENON*)(base + ctx.r3.u32);\n\n    if (g_achError != ACH_ERROR_SUCCESS)\n    {\n        static auto s_achErrorMessageResult = -1;\n\n        auto& message = Localise(\"Title_Message_LoadAchievementDataIOError\");\n        std::vector<std::string> options = { Localise(\"Common_Retry\"), Localise(\"Common_ContinueWithoutSaving\") };\n\n        switch (g_achError)\n        {\n            case ACH_ERROR_READ:\n            {\n                // Achievement data is unrecoverable, force user to overwrite it.\n                if (AchievementManager::BinStatus != EAchBinStatus::IOError)\n                {\n                    message = Localise(\"Title_Message_LoadAchievementDataCorrupt\");\n                    options[0] = Localise(\"Common_OK\");\n                    options.pop_back();\n                }\n\n                break;\n            }\n\n            case ACH_ERROR_WRITE:\n                message = Localise(\"Title_Message_SaveAchievementDataIOError\");\n                break;\n        }\n\n        if (MessageWindow::Open(message, &s_achErrorMessageResult, options, 0) == MSG_CLOSED)\n        {\n            auto operation = g_achError == ACH_ERROR_READ\n                ? Sonicteam::SaveDataTaskXENON::SaveDataOperation_ReadSaveData\n                : Sonicteam::SaveDataTaskXENON::SaveDataOperation_WriteSaveData;\n\n            switch (s_achErrorMessageResult)\n            {\n                // Retry / OK\n                case 0:\n                {\n                    // Create new achievement data file.\n                    AchievementManager::SaveBinary(true);\n\n                    // Display any future errors for new files.\n                    g_achErrorIgnored = ACH_ERROR_SUCCESS;\n\n                    break;\n                }\n\n                // Continue without saving.\n                // Ignore any future errors unrelated to corruption for this file.\n                case 1:\n                    g_achErrorIgnored = g_achError;\n                    break;\n            }\n\n            g_achError = ACH_ERROR_SUCCESS;\n            s_achErrorMessageResult = -1;\n\n            GuestToHostFunction<int>(sub_8238CB18, pSaveDataTaskXENON, operation);\n        }\n    }\n\n    __imp__sub_8238D5C8(ctx, base);\n}\n\nvoid SaveAlertThreeOptionRemoveDeviceSelect(PPCRegister& r5)\n{\n    auto options = (uint64_t*)g_memory.Translate(r5.u32 + 8);\n\n    // The \"Select storage device.\" option is always the\n    // second index for the three option alert windows.\n    options[2] = 0;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/TitleTask_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <os/logger.h>\n#include <ui/fader.h>\n#include <ui/message_window.h>\n#include <ui/options_menu.h>\n#include <user/achievement_manager.h>\n#include <user/config.h>\n#include <user/paths.h>\n#include <app.h>\n#include <exports.h>\n\nconstexpr auto TITLE_OPTION_OUTRO_FRAMES = (1.0 / 60.0) * 45.0;\nconstexpr auto TITLE_OUTRO_FRAMES = (1.0 / 60.0) * 25.0;\nconstexpr auto TITLE_OPTION_OUTRO_TOTAL_FRAMES = TITLE_OPTION_OUTRO_FRAMES + TITLE_OUTRO_FRAMES;\n\nstatic double g_titleProceedOutroTime{};\nstatic double g_titleExitOutroTime{};\n\nstatic bool g_quitMessageOpen{};\nstatic bool g_saveDataExists{};\n\nstatic bool g_isSecretDone{};\nstatic uint32_t g_secretFlags{};\n\nenum\n{\n    SECRET_NONE,\n    SECRET_UP = 1 << 0,\n    SECRET_DOWN = 1 << 1,\n    SECRET_LEFT = 1 << 2,\n    SECRET_RIGHT = 1 << 3,\n    SECRET_ALL = SECRET_UP | SECRET_DOWN | SECRET_LEFT | SECRET_RIGHT\n};\n\nbool ProcessQuitMessage(Sonicteam::TitleTask* pTitleTask)\n{\n    static auto s_quitMessageResult = -1;\n    static std::atomic<bool> s_faderBegun = false;\n\n    if (!g_quitMessageOpen)\n        return false;\n\n    if ((App::s_time - g_titleExitOutroTime) < TITLE_OUTRO_FRAMES)\n        return true;\n\n    static std::array<std::string, 2> s_options = { Localise(\"Common_Yes\"), Localise(\"Common_No\") };\n\n    if (MessageWindow::Open(Localise(\"Title_Message_Quit\"), &s_quitMessageResult, s_options, 1) == MSG_CLOSED)\n    {\n        if (s_quitMessageResult == 0)\n        {\n            Fader::FadeOut(1, []() { App::Exit(); });\n            s_faderBegun = true;\n        }\n        else\n        {\n            // Play Title_Open.\n            pTitleTask->m_MovieWaitTime = Sonicteam::TitleTask::ms_DefaultMovieWaitTime;\n            GuestToHostFunction<int>(sub_825119D8, pTitleTask, 0, 0);\n        }\n\n        g_quitMessageOpen = false;\n        s_quitMessageResult = -1;\n    }\n\n    return true;\n}\n\nvoid TitleTask_SetDefaultOption(PPCRegister& r3, PPCRegister& r4)\n{\n    if (!g_saveDataExists)\n        return;\n    \n    // Set default option to CONTINUE if save data exists.\n    reinterpret_cast<Sonicteam::TitleTask*>(g_memory.Translate(r3.u32))->m_SelectedIndex = 1;\n    r4.u32 = 5;\n}\n\nbool TitleTask_RedirectStateTransitionToOutroAnim(PPCRegister& r31)\n{\n    if (!g_saveDataExists || Config::DisableTitleInputDelay)\n        return false;\n\n    // Play Title_Close_02.\n    GuestToHostFunction<int>(sub_825119D8, r31.u32, 8, 0);\n\n    return true;\n}\n\n// Sonicteam::TitleTask::Update\nPPC_FUNC_IMPL(__imp__sub_825126A0);\nPPC_FUNC(sub_825126A0)\n{\n    auto pTitleTask = (Sonicteam::TitleTask*)(base + ctx.r3.u32);\n    auto deltaTime = ctx.f1.f64;\n\n    switch (pTitleTask->m_State)\n    {\n        case Sonicteam::TitleTask::TitleState_Open:\n        {\n            if (!Config::DisableTitleInputDelay)\n                break;\n\n            // Skip open animation.\n            GuestToHostFunction<int>(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_Wait);\n\n            break;\n        }\n\n        case Sonicteam::TitleTask::TitleState_Wait:\n        {\n            if (g_isSecretDone)\n                break;\n\n            if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0])\n            {\n                auto& rPadState = spInputManager->m_PadState;\n\n                if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadUp))\n                    g_secretFlags = SECRET_UP;\n\n                if ((g_secretFlags & SECRET_UP) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadDown))\n                    g_secretFlags |= SECRET_DOWN;\n\n                if ((g_secretFlags & SECRET_DOWN) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadLeft))\n                    g_secretFlags |= SECRET_LEFT;\n\n                if ((g_secretFlags & SECRET_LEFT) != 0 && rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadRight))\n                    g_secretFlags |= SECRET_RIGHT;\n            }\n\n            if (g_secretFlags == SECRET_ALL)\n            {\n                Game_PlaySound(\"totalring_count\");\n                g_isSecretDone = true;\n            }\n\n            break;\n        }\n\n        case Sonicteam::TitleTask::TitleState_OptionsWait:\n        {\n            if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0])\n            {\n                auto& rPadState = spInputManager->m_PadState;\n\n                if (g_secretFlags == SECRET_ALL &&\n                    rPadState.IsDown(Sonicteam::SoX::Input::KeyState_A) &&\n                    rPadState.IsDown(Sonicteam::SoX::Input::KeyState_Start))\n                {\n                    OptionsMenu::s_isDebugUnlocked = true;\n                }\n            }\n\n            break;\n        }\n\n        case Sonicteam::TitleTask::TitleState_OptionsProceed:\n        {\n            // Reset achievements on new game.\n            if (pTitleTask->m_SelectedIndex == 0)\n            {\n                LOGN(\"Resetting achievements...\");\n\n                AchievementManager::Reset();\n            }\n\n            if (!Config::DisableTitleInputDelay)\n            {\n                g_titleProceedOutroTime += deltaTime;\n\n                // Wait for outro animation to complete before entering menu.\n                if (g_titleProceedOutroTime > TITLE_OPTION_OUTRO_TOTAL_FRAMES)\n                    GuestToHostFunction<int>(sub_82511CA0, pTitleTask, 9);\n            }\n\n            break;\n        }\n\n        case Sonicteam::TitleTask::TitleState_Proceed:\n        {\n            g_saveDataExists = std::filesystem::exists(GetSaveFilePath(false));\n\n            // Redirect PRESS START proceed to options open.\n            if (g_saveDataExists)\n                GuestToHostFunction<int>(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_OptionsOpen);\n\n            break;\n        }\n    }\n\n    if (pTitleTask->m_State != Sonicteam::TitleTask::TitleState_OptionsProceed)\n        g_titleProceedOutroTime = 0.0;\n\n    auto skipStateUpdate = ProcessQuitMessage(pTitleTask);\n\n    if (skipStateUpdate)\n    {\n        GuestToHostFunction<int>(sub_82511B10, pTitleTask, deltaTime);\n\n        pTitleTask->m_MovieWaitTime = pTitleTask->m_MovieWaitTime - deltaTime;\n        pTitleTask->m_Field60 = 0;\n\n        if (pTitleTask->m_MovieWaitTime < 0.0f)\n            pTitleTask->m_MovieWaitTime = 0.0f;\n    }\n    else\n    {\n        if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0])\n        {\n            auto& rPadState = spInputManager->m_PadState;\n\n            if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B))\n            {\n                if (pTitleTask->m_State == Sonicteam::TitleTask::TitleState_Wait)\n                {\n                    // Play Title_Close_03.\n                    Game_PlaySound(\"deside\");\n                    GuestToHostFunction<int>(sub_825119D8, pTitleTask, 9, 0);\n\n                    g_titleExitOutroTime = App::s_time;\n                    g_quitMessageOpen = true;\n                }\n                else if (pTitleTask->m_State == Sonicteam::TitleTask::TitleState_OptionsWait)\n                {\n                    // Return to title wait.\n                    Game_PlaySound(\"window_close\");\n                    GuestToHostFunction<int>(sub_825119D8, pTitleTask, 1, 1);\n                    GuestToHostFunction<int>(sub_82511CA0, pTitleTask, (int)Sonicteam::TitleTask::TitleState_Wait);\n                }\n            }\n        }\n\n        __imp__sub_825126A0(ctx, base);\n    }\n}\n\nPPC_FUNC_IMPL(__imp__sub_82511540);\nPPC_FUNC(sub_82511540)\n{\n    if (Config::DisableTitleInputDelay)\n    {\n        ctx.r3.u32 = 1;\n        return;\n    }\n\n    __imp__sub_82511540(ctx, base);\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/aspect_ratio_patches.cpp",
    "content": "#include \"aspect_ratio_patches.h\"\n#include <api/Marathon.h>\n#include <gpu/video.h>\n#include <hid/hid.h>\n#include <patches/hook_event.h>\n#include <patches/loading_patches.h>\n#include <patches/MainMenuTask_patches.h>\n#include <ui/black_bar.h>\n#include <ui/game_window.h>\n#include <ui/imgui_utils.h>\n#include <ui/options_menu.h>\n#include <user/config.h>\n#include <app.h>\n\n// #define CORNER_DEBUG\n\nconstexpr float CHEVRON_INTRO_DURATION = 0.083f;\nconstexpr float CHEVRON_OUTRO_DURATION = 2.01666666666667f;\n\nstatic Mutex g_pathMutex;\nstatic std::map<const void*, XXH64_hash_t> g_paths{};\n\nstatic std::optional<CsdModifier> g_sceneModifier{};\nstatic std::optional<CsdModifier> g_castNodeModifier{};\nstatic std::optional<CsdModifier> g_castModifier{};\n\nstatic float g_fontPictureWidth{};\nstatic float g_fontPictureHeight{};\n\nstatic float g_corners[8]{};\nstatic bool g_cornerExtract{};\n\nstatic float g_radarMapX{};\nstatic float g_radarMapY{};\nstatic float g_radarMapCoverWidth{};\nstatic float g_radarMapCoverHeight{};\n\nstatic float g_podBaseRightX{};\n\nstatic float g_bgArrowsEnd{};\nstatic float g_fgArrowsEnd{};\nstatic float g_chevronLoopTime{};\n\nstruct ChevronAnim\n{\n    uint8_t EndAlpha{};\n\n    float IntroDuration{};\n    float IntroStartTime{};\n\n    float OutroDuration{};\n    float OutroStartTime{};\n\n    float TimeElapsed{};\n\n    uint8_t CurrentAlpha{};\n    float CurrentOffsetX{};\n\n    void Reset()\n    {\n        TimeElapsed = 0.0f;\n        CurrentAlpha = 0;\n    }\n\n    void Update(float deltaTime)\n    {\n        TimeElapsed += deltaTime;\n\n        if (TimeElapsed < IntroStartTime && TimeElapsed < OutroStartTime)\n        {\n            CurrentAlpha = std::lerp(0, EndAlpha, std::clamp((TimeElapsed - IntroStartTime) / IntroDuration, 0.0f, 1.0f));\n        }\n        else\n        {\n            CurrentAlpha = std::lerp(EndAlpha, 0, std::clamp((TimeElapsed - OutroStartTime) / OutroDuration, 0.0f, 1.0f));\n        }\n\n        CurrentOffsetX = std::lerp(0.0f, 5.0f, std::clamp(TimeElapsed / g_chevronLoopTime, 0.0f, 1.0f));\n\n        if (TimeElapsed < g_chevronLoopTime)\n            return;\n\n        Reset();\n    }\n};\n\nstatic std::unordered_map<int32_t, ChevronAnim> g_bgArrows{};\nstatic std::unordered_map<int32_t, ChevronAnim> g_fgArrows{};\n\nstatic class LoadingPillarboxEvent : public HookEvent\n{\npublic:\n    void Update(float deltaTime) override\n    {\n        if (g_aspectRatio > WIDE_ASPECT_RATIO)\n            BlackBar::Show();\n    }\n}\ng_loadingPillarboxEvent{};\n\nstatic class ChevronAnimResetEvent : public ContextHookEvent<Sonicteam::MainMenuTask>\n{\n    float m_chevronAspectRatio{};\n\npublic:\n    void Update(Sonicteam::MainMenuTask* pThis, float deltaTime) override\n    {\n        if (g_aspectRatio <= WIDE_ASPECT_RATIO)\n            return;\n\n        auto aspectRatioChanged = false;\n\n        if (g_aspectRatio != m_chevronAspectRatio)\n            aspectRatioChanged = true;\n\n        m_chevronAspectRatio = g_aspectRatio;\n\n        if (pThis->m_State == Sonicteam::MainMenuTask::MainMenuState_MainMenuBack ||\n            pThis->m_State == Sonicteam::MainMenuTask::MainMenuState_AudioRoom    ||\n            pThis->m_State == Sonicteam::MainMenuTask::MainMenuState_TheaterRoom  ||\n            aspectRatioChanged)\n        {\n            g_bgArrows.clear();\n            g_fgArrows.clear();\n        }\n\n        for (auto& arrow : g_bgArrows)\n            arrow.second.Update(deltaTime);\n\n        for (auto& arrow : g_fgArrows)\n            arrow.second.Update(deltaTime);\n    }\n}\ng_chevronAnimResetEvent{};\n\nfloat ComputeScale(float aspectRatio)\n{\n    return ((aspectRatio * 720.0f) / 1280.0f) / sqrt((aspectRatio * 720.0f) / 1280.0f);\n}\n\nvoid AspectRatioPatches::Init()\n{\n    LoadingPatches::Events.push_back(&g_loadingPillarboxEvent);\n    MainMenuTaskPatches::s_events.push_back(&g_chevronAnimResetEvent);\n}\n\nvoid AspectRatioPatches::ComputeOffsets()\n{\n    float width = Video::s_viewportWidth;\n    float height = Video::s_viewportHeight;\n\n    g_aspectRatio = width / height;\n    g_aspectRatioGameplayScale = 1.0f;\n\n    if (g_aspectRatio >= NARROW_ASPECT_RATIO)\n    {\n        g_aspectRatioOffsetX = (width - height * WIDE_ASPECT_RATIO) / 2.0f;\n        g_aspectRatioOffsetY = 0.0f;\n        g_aspectRatioScale = height / 720.0f;\n\n        // keep same scale above Steam Deck aspect ratio\n        if (g_aspectRatio < WIDE_ASPECT_RATIO)\n        {\n            // interpolate to original 4:3 scale\n            float steamDeckScale = g_aspectRatio / WIDE_ASPECT_RATIO;\n            float narrowScale = ComputeScale(NARROW_ASPECT_RATIO);\n            float lerpFactor = std::clamp((g_aspectRatio - NARROW_ASPECT_RATIO) / (STEAM_DECK_ASPECT_RATIO - NARROW_ASPECT_RATIO), 0.0f, 1.0f);\n\n            g_aspectRatioGameplayScale = narrowScale + (steamDeckScale - narrowScale) * lerpFactor;\n        }\n    }\n    else\n    {\n        // 4:3 crop\n        g_aspectRatioOffsetX = (width - width * NARROW_ASPECT_RATIO) / 2.0f;\n        g_aspectRatioOffsetY = (height - width / NARROW_ASPECT_RATIO) / 2.0f;\n        g_aspectRatioScale = width / 960.0f;\n        g_aspectRatioGameplayScale = ComputeScale(NARROW_ASPECT_RATIO);\n    }\n\n    g_aspectRatioMultiplayerOffsetX = g_aspectRatioOffsetX / 2.0f;\n    g_aspectRatioNarrowScale = std::clamp((g_aspectRatio - NARROW_ASPECT_RATIO) / (WIDE_ASPECT_RATIO - NARROW_ASPECT_RATIO), 0.0f, 1.0f);\n    g_aspectRatioNarrowMargin = std::lerp(50.0f, 0.0f, g_aspectRatioNarrowScale);\n    g_horzCentre = g_aspectRatioOffsetX + 640.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n    g_vertCentre = g_aspectRatioOffsetY + 360.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n    g_radarMapScale = 256 * g_aspectRatioScale * g_aspectRatioGameplayScale;\n}\n\nvoid EmplacePath(const void* key, const std::string_view& value)\n{\n    std::lock_guard lock(g_pathMutex);\n    g_paths.emplace(key, HashStr(value));\n}\n\nvoid TraverseCast(Chao::CSD::Scene* scene, uint32_t castNodeIndex, Chao::CSD::CastNode* castNode, uint32_t castIndex, const std::string& parentPath)\n{\n    if (castIndex == ~0)\n        return;\n\n    TraverseCast(scene, castNodeIndex, castNode, castNode->pCastLinks[castIndex].SiblingCastIndex, parentPath);\n\n    std::string path = parentPath;\n\n    for (size_t i = 0; i < scene->CastCount; i++)\n    {\n        auto& index = scene->pCastIndices[i];\n        if (index.CastNodeIndex == castNodeIndex && index.CastIndex == castIndex)\n        {\n            path += index.pCastName;\n            break;\n        }\n    }\n\n    EmplacePath(castNode->pCasts[castIndex].get(), path);\n\n    if (castNode->RootCastIndex == castIndex)\n        EmplacePath(castNode, path);\n\n    path += \"/\";\n\n    TraverseCast(scene, castNodeIndex, castNode, castNode->pCastLinks[castIndex].ChildCastIndex, path);\n\n    // LOGFN_UTILITY(\"CSD hierarchy: {}\", path);\n}\n\nvoid TraverseScene(Chao::CSD::Scene* scene, std::string path)\n{\n    EmplacePath(scene, path);\n    path += \"/\";\n\n    for (size_t i = 0; i < scene->CastNodeCount; i++)\n    {\n        auto& castNode = scene->pCastNodes[i];\n        TraverseCast(scene, i, &castNode, castNode.RootCastIndex, path);\n    }\n}\n\nvoid TraverseSceneNode(Chao::CSD::SceneNode* sceneNode, std::string path)\n{\n    EmplacePath(sceneNode, path);\n    path += \"/\";\n\n    for (size_t i = 0; i < sceneNode->SceneCount; i++)\n    {\n        auto& sceneIndex = sceneNode->pSceneIndices[i];\n        TraverseScene(sceneNode->pScenes[sceneIndex.SceneIndex], path + sceneIndex.pSceneName.get());\n    }\n\n    for (size_t i = 0; i < sceneNode->SceneNodeCount; i++)\n    {\n        auto& sceneNodeIndex = sceneNode->pSceneNodeIndices[i];\n        TraverseSceneNode(&sceneNode->pSceneNodes[sceneNodeIndex.SceneNodeIndex], path + sceneNodeIndex.pSceneNodeName.get());\n    }\n}\n\n// Sonicteam::CsdResource::MakeResource\nPPC_FUNC_IMPL(__imp__sub_82617570);\nPPC_FUNC(sub_82617570)\n{\n    auto pName = reinterpret_cast<char*>(base + PPC_LOAD_U32(ctx.r4.u32 + 4));\n\n    __imp__sub_82617570(ctx, base);\n\n    if (!ctx.r3.u32)\n        return;\n\n    auto ppCsdObject = PPC_LOAD_U32(ctx.r3.u32);\n\n    if (!ppCsdObject)\n        return;\n\n    auto pCsdObject = reinterpret_cast<Sonicteam::CsdObject*>(base + ppCsdObject);\n\n    if (!pCsdObject || !pCsdObject->m_pCsdProject)\n        return;\n\n    LOGFN_UTILITY(\"CSD loaded: {} (0x{:08X})\", pName, (uint64_t)pCsdObject->m_pCsdProject.get());\n\n    static const char* s_languages[7] =\n    {\n        nullptr, // Maps to ELanguages enum.\n        \"_English\",\n        \"_Japanese\",\n        \"_German\",\n        \"_French\",\n        \"_Spanish\",\n        \"_Italian\"\n    };\n\n    auto pSuffix = s_languages[(int)Config::Language.Value];\n\n    auto nameLen = strlen(pName);\n    auto suffixLen = strlen(pSuffix);\n\n    // Truncate language names to redirect CSD modifiers.\n    if (suffixLen < nameLen)\n    {\n        if (strcmpIgnoreCase(pName + nameLen - suffixLen, pSuffix))\n        {\n            pName[nameLen - suffixLen] = '\\0';\n\n            LOGFN_UTILITY(\"CSD modifier(s) redirected: {}\", pName);\n        }\n    }\n\n    TraverseSceneNode(pCsdObject->m_pCsdProject->m_pResource->pRootNode, pName);\n}\n\n// Chao::CSD::CMemoryAlloc::Free\nPPC_FUNC_IMPL(__imp__sub_82656650);\nPPC_FUNC(sub_82656650)\n{\n    if (ctx.r4.u32 != NULL && PPC_LOAD_U32(ctx.r4.u32) == 0x4649584E && PPC_LOAD_U32(ctx.r4.u32 + 0x20) == 0x6E43504A) // NXIF, nCPJ\n    {\n        uint32_t fileSize = PPC_LOAD_U32(ctx.r4.u32 + 0x14);\n    \n        std::lock_guard lock(g_pathMutex);\n        const uint8_t* key = base + ctx.r4.u32;\n    \n        auto lower = g_paths.lower_bound(key);\n        auto upper = g_paths.lower_bound(key + fileSize);\n    \n        g_paths.erase(lower, upper);\n\n        LOGFN_UTILITY(\"CSD freed: 0x{:08X}\", (uint64_t)key);\n    }\n\n    __imp__sub_82656650(ctx, base);\n}\n\n// Chao::CSD::Scene::Render\nPPC_FUNC_IMPL(__imp__sub_828C8F60);\nPPC_FUNC(sub_828C8F60)\n{\n    auto pScene = (Chao::CSD::Scene*)(base + ctx.r3.u32);\n\n    g_sceneModifier = FindCsdModifier(ctx.r3.u32);\n\n    if (g_sceneModifier.has_value())\n    {\n        if ((g_sceneModifier->Flags & CSD_MODIFIER_ULTRAWIDE_ONLY) != 0 && g_aspectRatio <= WIDE_ASPECT_RATIO)\n            g_sceneModifier->Flags &= (~g_sceneModifier->Flags) | CSD_MODIFIER_ULTRAWIDE_ONLY;\n\n        if ((g_sceneModifier->Flags & CSD_SCENE_DISABLE_MOTION) != 0)\n            pScene->FPS = 0;\n\n        if (g_aspectRatio > WIDE_ASPECT_RATIO)\n        {\n            if ((g_sceneModifier->Flags & (CSD_OFFSET_SCALE_LEFT | CSD_OFFSET_SCALE_RIGHT | CSD_CORNER_EXTRACT)) != 0)\n            {\n                auto r3 = ctx.r3;\n                auto r4 = ctx.r4;\n                auto r5 = ctx.r5;\n                auto r6 = ctx.r6;\n\n                // Queue draw calls, but don't actually draw anything. We just want to extract the corner.\n                g_cornerExtract = true;\n                __imp__sub_828C8F60(ctx, base);\n                g_cornerExtract = false;\n\n#ifdef CORNER_DEBUG\n                if (g_sceneModifier->CornerMax == FLT_MAX)\n                {\n                    fmt::print(\"Corners: \");\n                    for (auto corner : g_corners)\n                        fmt::print(\"{} \", corner);\n\n                    fmt::println(\"\");\n                }\n#endif\n\n                ctx.r3 = r3;\n                ctx.r4 = r4;\n                ctx.r5 = r5;\n                ctx.r6 = r6;\n            }\n        }\n    }\n\n    __imp__sub_828C8F60(ctx, base);\n}\n\nvoid RenderCsdCastNodeMidAsmHook(PPCRegister& r10, PPCRegister& r27)\n{\n    g_castNodeModifier = FindCsdModifier(r10.u32 + r27.u32);\n}\n\nvoid RenderCsdCastMidAsmHook(PPCRegister& r4)\n{\n    g_castModifier = FindCsdModifier(r4.u32);\n}\n\nvoid Draw(PPCContext& ctx, uint8_t* base, PPCFunc* original, uint32_t stride)\n{\n    CsdModifier modifier{};\n\n    auto vpWidth = float(Video::s_viewportWidth);\n    auto vpHeight = float(Video::s_viewportHeight);\n\n    if (g_castModifier.has_value())\n    {\n        modifier = g_castModifier.value();\n    }\n    else if (g_castNodeModifier.has_value())\n    {\n        modifier = g_castNodeModifier.value();\n    }\n    else if (g_sceneModifier.has_value())\n    {\n        modifier = g_sceneModifier.value();\n    }\n\n    // Hide original button window whilst the options menu is visible.\n    if ((modifier.Flags & CSD_BUTTON_WINDOW) != 0 && MainMenuTaskPatches::s_hideButtonWindow)\n        return;\n\n    // Remove all flags if the aspect ratio is above 16:9.\n    if ((modifier.Flags & CSD_MODIFIER_ULTRAWIDE_ONLY) != 0 && g_aspectRatio <= WIDE_ASPECT_RATIO)\n        modifier.Flags &= (~modifier.Flags) | CSD_MODIFIER_ULTRAWIDE_ONLY;\n\n    // Remove all flags if the aspect ratio is below 16:9.\n    if ((modifier.Flags & CSD_MODIFIER_NARROW_ONLY) != 0 && g_aspectRatio >= WIDE_ASPECT_RATIO)\n        modifier.Flags &= (~modifier.Flags) | CSD_MODIFIER_NARROW_ONLY;\n    \n    if ((modifier.Flags & CSD_SKIP) != 0)\n    {\n        if ((modifier.Flags & CSD_CHEVRON) != 0)\n        {\n            // Don't draw non-extended arrows at ultrawide.\n            if (g_aspectRatio > WIDE_ASPECT_RATIO)\n                return;\n        }\n        else\n        {\n            // Skip drawing this cast.\n            return;\n        }\n    }\n\n    if (g_cornerExtract)\n    {\n        if ((modifier.Flags & (CSD_STORE_LEFT_CORNER | CSD_STORE_RIGHT_CORNER)) != 0)\n        {\n            uint32_t vertexIndex = ((modifier.Flags & CSD_STORE_LEFT_CORNER) != 0) ? 0 : 3;\n            g_corners[modifier.CornerIndex] = *reinterpret_cast<be<float>*>(base + ctx.r4.u32 + vertexIndex * stride);\n        }\n\n        return;\n    }\n\n    // Draw black bars if this cast is drawn.\n    if ((modifier.Flags & CSD_BLACK_BAR) != 0)\n        BlackBar::Show();\n\n    // Prohibit black bars from being drawn if this cast is drawn.\n    if ((modifier.Flags & CSD_PROHIBIT_BLACK_BAR) != 0)\n        BlackBar::Hide();\n\n    if (Config::UIAlignmentMode == EUIAlignmentMode::Centre || BlackBar::IsVisible())\n    {\n        // Prevent chevron arrows from being unaligned by centred aspect ratio.\n        if ((modifier.Flags & CSD_CHEVRON) == 0)\n        {\n            if (g_aspectRatio > WIDE_ASPECT_RATIO)\n            {\n                // Remove horizontal alignments at wide aspect ratios.\n                modifier.Flags &= ~(CSD_ALIGN_LEFT | CSD_ALIGN_RIGHT);\n            }\n            else if (g_aspectRatio < WIDE_ASPECT_RATIO)\n            {\n                // Remove vertical alignments at narrow aspect ratios.\n                modifier.Flags &= ~(CSD_ALIGN_TOP | CSD_ALIGN_BOTTOM);\n            }\n        }\n    }\n\n    // Reserve stack space for vertices.\n    auto size = ctx.r5.u32 * stride;\n    ctx.r1.u32 -= size;\n\n    // Copy vertices to stack.\n    auto stack = base + ctx.r1.u32;\n    memcpy(stack, base + ctx.r4.u32, size);\n\n    struct CSDVertex\n    {\n        be<float> X;\n        be<float> Y;\n        be<uint32_t> Colour;\n        be<float> U;\n        be<float> V;\n    };\n\n    auto getVertex = [&](size_t index)\n    {\n        return reinterpret_cast<CSDVertex*>(stack + (index * stride));\n    };\n\n    auto offsetX = 0.0f;\n    auto offsetY = 0.0f;\n    auto pivotX = 0.0f;\n    auto pivotY = 0.0f;\n    auto scaleX = 1.0f;\n    auto scaleY = 1.0f;\n\n    auto needsStretch = g_aspectRatio >= WIDE_ASPECT_RATIO;\n\n    if (needsStretch && (modifier.Flags & CSD_STRETCH_HORIZONTAL) != 0)\n    {\n        scaleX = vpWidth / 1280.0f;\n    }\n    else\n    {\n        scaleX = g_aspectRatioScale;\n\n        if (needsStretch && (modifier.Flags & CSD_UNSTRETCH_HORIZONTAL) != 0)\n        {\n            pivotX = getVertex(0)->X;\n            offsetX = pivotX * vpWidth / 1280.0f;\n        }\n        else\n        {\n            if ((modifier.Flags & CSD_ALIGN_RIGHT) != 0)\n            {\n                offsetX = g_aspectRatioOffsetX * 2.0f;\n\n                if ((modifier.Flags & CSD_MULTIPLAYER) != 0 && g_aspectRatio > WIDE_ASPECT_RATIO)\n                    offsetX = g_aspectRatioMultiplayerOffsetX * 2.0f;\n            }\n            else if ((modifier.Flags & CSD_ALIGN_LEFT) == 0)\n                offsetX = g_aspectRatioOffsetX;\n\n            // Don't offset arrows at 16:9 or narrower.\n            if ((modifier.Flags & CSD_CHEVRON) != 0 && g_aspectRatio <= WIDE_ASPECT_RATIO)\n                offsetX = 0.0f;\n\n            if ((modifier.Flags & CSD_SCALE) != 0)\n            {\n                scaleX *= g_aspectRatioGameplayScale;\n\n                if ((modifier.Flags & CSD_ALIGN_RIGHT) != 0)\n                    offsetX += 1280.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n                else if ((modifier.Flags & CSD_ALIGN_LEFT) == 0)\n                    offsetX += g_horzCentre - g_aspectRatioOffsetX;\n\n                offsetX += pivotX * g_aspectRatioScale;\n            }\n        }\n    }\n\n    if ((modifier.Flags & CSD_STRETCH_VERTICAL) != 0)\n    {\n        scaleY = vpHeight / 720.0f;\n    }\n    else\n    {\n        scaleY = g_aspectRatioScale;\n\n        if ((modifier.Flags & CSD_ALIGN_BOTTOM) != 0)\n            offsetY = g_aspectRatioOffsetY * 2.0f;\n        else if ((modifier.Flags & CSD_ALIGN_TOP) == 0)\n            offsetY = g_aspectRatioOffsetY;\n\n        if ((modifier.Flags & CSD_SCALE) != 0)\n        {\n            scaleY *= g_aspectRatioGameplayScale;\n\n            if ((modifier.Flags & CSD_ALIGN_BOTTOM) != 0)\n                offsetY += 720.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n            else if ((modifier.Flags & CSD_ALIGN_TOP) == 0)\n                offsetY += g_vertCentre - g_aspectRatioOffsetY;\n\n            offsetY += pivotY * g_aspectRatioScale;\n        }\n    }\n\n    // Offset cast to movie aspect ratio boundaries.\n    if ((modifier.Flags & CSD_MOVIE) != 0)\n    {\n        if (g_aspectRatio > g_aspectRatioMovie)\n        {\n            offsetX -= (vpWidth - (vpHeight * g_aspectRatioMovie)) / 2.0f;\n        }\n        else\n        {\n            offsetY -= (vpHeight - (vpWidth / g_aspectRatioMovie)) / 2.0f;\n        }\n    }\n\n    if (g_aspectRatio > WIDE_ASPECT_RATIO)\n    {\n        CsdModifier offsetScaleModifier{};\n\n        auto corner = 0.0f;\n\n        if (g_castModifier.has_value())\n        {\n            offsetScaleModifier = g_castModifier.value();\n            corner = getVertex(((offsetScaleModifier.Flags & CSD_STORE_LEFT_CORNER) != 0) ? 0 : 3)->X;\n        }\n\n        if (offsetScaleModifier.CornerMax == 0.0f && g_castNodeModifier.has_value())\n        {\n            offsetScaleModifier = g_castNodeModifier.value();\n            corner = g_corners[offsetScaleModifier.CornerIndex];\n        }\n\n        if (offsetScaleModifier.CornerMax == 0.0f && g_sceneModifier.has_value())\n        {\n            offsetScaleModifier = g_sceneModifier.value();\n            corner = g_corners[offsetScaleModifier.CornerIndex];\n        }\n\n#ifdef CORNER_DEBUG\n        if ((offsetScaleModifier.Flags & (CSD_OFFSET_SCALE_LEFT | CSD_OFFSET_SCALE_RIGHT)) != 0 && offsetScaleModifier.CornerMax == FLT_MAX)\n            fmt::println(\"Corner: {}\", corner);\n#endif\n\n        if ((offsetScaleModifier.Flags & CSD_OFFSET_SCALE_LEFT) != 0)\n            offsetX *= corner / offsetScaleModifier.CornerMax;\n        else if ((offsetScaleModifier.Flags & CSD_OFFSET_SCALE_RIGHT) != 0)\n            offsetX = vpWidth - (vpWidth - offsetX) * (1280.0f - corner) / (1280.0f - offsetScaleModifier.CornerMax);\n    }\n\n    auto firstX = 0.0f;\n    auto firstY = 0.0f;\n    auto lastX = 0.0f;\n    auto lastY = 0.0f;\n    auto width = 0.0f;\n    auto height = 0.0f;\n\n    for (size_t i = 0; i < ctx.r5.u32; i++)\n    {\n        auto vertex = getVertex(i);\n\n        auto x = offsetX + (vertex->X - pivotX) * scaleX;\n        auto y = offsetY + (vertex->Y - pivotY) * scaleY;\n\n        if ((modifier.Flags & CSD_EXTEND_LEFT) != 0 && (i == 0 || i == 1))\n        {\n            x = std::min(x, 0.0f);\n        }\n        else if ((modifier.Flags & CSD_EXTEND_RIGHT) != 0 && (i == 2 || i == 3))\n        {\n            // Preserve Audio Room \"pod\" base right edge.\n            if ((modifier.Flags & CSD_POD_BASE) != 0)\n                g_podBaseRightX = x;\n\n            x = std::max(x, vpWidth);\n        }\n\n        switch (i)\n        {\n            case 0: firstX = x; break;\n            case 1: lastY  = y; break;\n            case 2: firstY = y; break;\n            case 3: lastX  = x; break;\n        }\n\n        if ((modifier.Flags & CSD_RADARMAP) != 0)\n        {\n            g_radarMapX = x;\n            g_radarMapY = y;\n        }\n\n        vertex->X = round(x);\n        vertex->Y = round(y);\n    }\n\n    width = lastX - firstX;\n    height = lastY - firstY;\n\n    if ((modifier.Flags & CSD_RADARMAP) != 0)\n    {\n        g_radarMapCoverWidth = width;\n        g_radarMapCoverHeight = height;\n    }\n\n    auto flipHorizontally = [&]()\n    {\n        getVertex(0)->X = getVertex(0)->X + width;\n        getVertex(1)->X = getVertex(1)->X + width;\n        getVertex(2)->X = getVertex(2)->X - width;\n        getVertex(3)->X = getVertex(3)->X - width;\n    };\n\n    auto flipVertically = [&]()\n    {\n        getVertex(0)->Y = getVertex(0)->Y + height;\n        getVertex(1)->Y = getVertex(1)->Y - height;\n        getVertex(2)->Y = getVertex(2)->Y + height;\n        getVertex(3)->Y = getVertex(3)->Y - height;\n    };\n\n    auto applyUVModifier = [&](CsdUVs uvModifier)\n    {\n        getVertex(0)->U = getVertex(0)->U + uvModifier.U0;\n        getVertex(0)->V = getVertex(0)->V + uvModifier.V0;\n        getVertex(1)->U = getVertex(1)->U + uvModifier.U1;\n        getVertex(1)->V = getVertex(1)->V + uvModifier.V1;\n        getVertex(2)->U = getVertex(2)->U + uvModifier.U2;\n        getVertex(2)->V = getVertex(2)->V + uvModifier.V2;\n        getVertex(3)->U = getVertex(3)->U + uvModifier.U3;\n        getVertex(3)->V = getVertex(3)->V + uvModifier.V3;\n    };\n\n    auto applyColourModifier = [&](CsdColours colourModifier)\n    {\n        getVertex(0)->Colour = colourModifier.C0;\n        getVertex(1)->Colour = colourModifier.C1;\n        getVertex(2)->Colour = colourModifier.C2;\n        getVertex(3)->Colour = colourModifier.C3;\n    };\n\n    if ((modifier.Flags & CSD_UV_MODIFIER) != 0)\n        applyUVModifier(modifier.UVs);\n\n    if ((modifier.Flags & CSD_COLOUR_MODIFIER) != 0)\n        applyColourModifier(modifier.Colours);\n\n    // Don't repeat arrows at 16:9 or narrower.\n    if ((modifier.Flags & CSD_CHEVRON) != 0 && g_aspectRatio <= WIDE_ASPECT_RATIO)\n        modifier.Flags &= ~(CSD_REPEAT_LEFT | CSD_REPEAT_RIGHT);\n\n    auto isRepeatLeft = (modifier.Flags & CSD_REPEAT_LEFT) != 0;\n    auto isRepeatRight = (modifier.Flags & CSD_REPEAT_RIGHT) != 0;\n\n    auto isMainMenuPanels = (modifier.Flags &\n        (CSD_MAIN_MENU_PARTS_CAST_0221 |\n         CSD_MAIN_MENU_PARTS_CAST_0222 |\n         CSD_MAIN_MENU_PARTS_CAST_0226 |\n         CSD_MAIN_MENU_PARTS_CAST_0227)) != 0;\n\n    auto r3 = ctx.r3;\n    auto r5 = ctx.r5;\n\n    auto drawOriginal = [&]()\n    {\n        ctx.r3 = r3;\n        ctx.r4 = ctx.r1;\n        ctx.r5 = r5;\n        original(ctx, base);\n    };\n\n    if (isRepeatLeft || isRepeatRight)\n    {\n        auto isFlipHorz = (modifier.Flags & CSD_REPEAT_FLIP_HORIZONTAL) != 0;\n        auto isFlipVert = (modifier.Flags & CSD_REPEAT_FLIP_VERTICAL) != 0;\n\n        auto applyRepeatModifiers = [&]()\n        {\n            if (isFlipHorz)\n                flipHorizontally();\n\n            if (isFlipVert)\n                flipVertically();\n\n            if ((modifier.Flags & CSD_REPEAT_UV_MODIFIER) != 0)\n                applyUVModifier(modifier.RepeatUVs);\n\n            if ((modifier.Flags & CSD_REPEAT_COLOUR_MODIFIER) != 0)\n                applyColourModifier(modifier.RepeatColours);\n        };\n\n        if (isRepeatLeft)\n        {\n            auto x = getVertex(2)->X;\n            auto arrowIndex = 0;\n            auto arrowCount = 0;\n            auto arrowWidth = (width - (width / 2.5f));\n\n            if ((modifier.Flags & CSD_CHEVRON) != 0)\n            {\n                // Shift root arrow forwards to use entirely custom arrows.\n                for (size_t i = 0; i < r5.u32; i++)\n                    getVertex(i)->X = getVertex(i)->X + arrowWidth;\n\n                // Compute number of foreground arrows.\n                while (x > 0.0f)\n                {\n                    x = x - arrowWidth;\n                    arrowCount++;\n                }\n\n                g_fgArrowsEnd = (CHEVRON_INTRO_DURATION / 2.0f) * float(arrowCount + 1);\n\n                // Reset X to first vertex.\n                x = getVertex(0)->X;\n            }\n\n            while (x > 0.0f)\n            {\n                drawOriginal();\n\n                if ((modifier.Flags & CSD_CHEVRON) != 0)\n                {\n                    ///////////////////////////////////////////////////////////\n                    // Clone foreground arrows to recreate chevron animation //\n                    ///////////////////////////////////////////////////////////\n\n                    // These values are based off the animation in background.xncp.\n                    auto endAlpha = (uint8_t)std::lerp(30, 10, x / vpWidth);\n                    auto introDuration = CHEVRON_INTRO_DURATION;\n                    auto introStartTime = (introDuration / 2.0f) * float(arrowIndex + 1) + g_bgArrowsEnd;\n                    auto outroDuration = CHEVRON_OUTRO_DURATION;\n                    auto outroStartTime = (g_fgArrowsEnd + introStartTime) + 0.5f;\n\n                    // This will be set by the last foreground arrow in the\n                    // sequence, therefore making it the loop end point.\n                    g_chevronLoopTime = outroStartTime + outroDuration;\n\n                    // Store current arrow animation progress.\n                    auto& arrow = g_fgArrows[arrowIndex];\n                    arrow.EndAlpha = endAlpha;\n                    arrow.IntroDuration = introDuration;\n                    arrow.IntroStartTime = introStartTime;\n                    arrow.OutroDuration = outroDuration;\n                    arrow.OutroStartTime = outroStartTime;\n\n                    // Animate arrow position and alpha.\n                    for (size_t i = 0; i < r5.u32; i++)\n                    {\n                        getVertex(i)->X = (getVertex(i)->X - arrowWidth) - g_bgArrows[arrowIndex].CurrentOffsetX;\n                        getVertex(i)->Colour = 0xFFFFFF00 | g_fgArrows[arrowIndex].CurrentAlpha;\n                    }\n\n                    arrowIndex++;\n                }\n                else\n                {\n                    // Move cloned cast to left edge of existing cast.\n                    for (size_t i = 0; i < r5.u32; i++)\n                        getVertex(i)->X = getVertex(i)->X - width;\n                }\n\n                // Update loop condition.\n                x = getVertex(2)->X;\n\n                applyRepeatModifiers();\n\n                if ((modifier.Flags & CSD_REPEAT_EXTEND) != 0)\n                {\n                    for (size_t i = 0; i < r5.u32; i++)\n                    {\n                        if (i == (isFlipHorz ? 2 : 0) || i == (isFlipHorz ? 3 : 1))\n                            getVertex(i)->X = std::min(getVertex(i)->X.get(), 0.0f);\n                    }\n                }\n            }\n        }\n\n        if (isRepeatRight)\n        {\n            auto x = getVertex(0)->X;\n            auto arrowIndex = 0;\n            auto arrowCount = 0;\n            auto arrowWidth = (width - (width / 4.0f));\n\n            if ((modifier.Flags & CSD_CHEVRON) != 0)\n            {\n                // Shift root arrow backwards to use entirely custom arrows.\n                for (size_t i = 0; i < r5.u32; i++)\n                    getVertex(i)->X = getVertex(i)->X - arrowWidth;\n\n                // Compute number of background arrows.\n                while (x < vpWidth)\n                {\n                    x = x + arrowWidth;\n                    arrowCount++;\n                }\n\n                g_bgArrowsEnd = (CHEVRON_INTRO_DURATION / 2.0f) * float(arrowCount + 1);\n\n                // Reset X to first vertex.\n                x = getVertex(0)->X;\n            }\n\n            while (x < vpWidth)\n            {\n                drawOriginal();\n\n                if ((modifier.Flags & CSD_CHEVRON) != 0)\n                {\n                    ///////////////////////////////////////////////////////////\n                    // Clone background arrows to recreate chevron animation //\n                    ///////////////////////////////////////////////////////////\n\n                    // These values are based off the animation in background.xncp.\n                    auto endAlpha = (uint8_t)std::lerp(10, 35, x / vpWidth);\n                    auto introDuration = CHEVRON_INTRO_DURATION;\n                    auto introStartTime = (introDuration / 2.0f) * float(arrowIndex + 1);\n                    auto outroDuration = CHEVRON_OUTRO_DURATION;\n                    auto outroStartTime = (g_bgArrowsEnd + introStartTime) + 0.5f;\n\n                    // Store current arrow animation progress.\n                    auto& arrow = g_bgArrows[arrowIndex];\n                    arrow.EndAlpha = endAlpha;\n                    arrow.IntroDuration = introDuration;\n                    arrow.IntroStartTime = introStartTime;\n                    arrow.OutroDuration = outroDuration;\n                    arrow.OutroStartTime = outroStartTime;\n\n                    // Animate arrow position and alpha.\n                    for (size_t i = 0; i < r5.u32; i++)\n                    {\n                        getVertex(i)->X = (getVertex(i)->X + arrowWidth) + g_bgArrows[arrowIndex].CurrentOffsetX;\n                        getVertex(i)->Colour = 0xFFFFFF00 | g_bgArrows[arrowIndex].CurrentAlpha;\n                    }\n\n                    arrowIndex++;\n                }\n                else\n                {\n                    // Move cloned cast to right edge of existing cast.\n                    for (size_t i = 0; i < r5.u32; i++)\n                        getVertex(i)->X = getVertex(i)->X + width;\n                }\n\n                // Update loop condition.\n                x = getVertex(0)->X;\n\n                applyRepeatModifiers();\n\n                // Extend to the right of the screen.\n                if ((modifier.Flags & CSD_REPEAT_EXTEND) != 0)\n                {\n                    for (size_t i = 0; i < r5.u32; i++)\n                    {\n                        if (i == (isFlipHorz ? 0 : 2) || i == (isFlipHorz ? 1 : 3))\n                            getVertex(i)->X = std::max(getVertex(i)->X.get(), vpWidth);\n                    }\n                }\n            }\n        }\n    }\n    else\n    {\n        if (!isMainMenuPanels)\n            drawOriginal();\n\n        // Clone Audio Room \"pod\" left edge to fill\n        // in the rest of the box at ultrawide.\n        if ((modifier.Flags & CSD_POD_CLONE) != 0 && g_aspectRatio > WIDE_ASPECT_RATIO)\n        {\n            auto v0 = getVertex(0);\n            auto v1 = getVertex(1);\n            auto v2 = getVertex(2);\n            auto v3 = getVertex(3);\n\n            // Shift cloned element to base edge.\n            v0->X = g_podBaseRightX;\n            v1->X = g_podBaseRightX;\n\n            // Stretch cloned element to screen edge.\n            v2->X = vpWidth;\n            v3->X = vpWidth;\n\n            // Shift UVs to remove cloned element edge line.\n            v0->U = v0->U + 0.008f;\n            v0->V = v0->V + 0.008f;\n            v1->U = v1->U + 0.008f;\n            v1->V = v1->V + 0.008f;\n\n            drawOriginal();\n        }\n\n        // Don't adjust CRI logo at 16:9 or if the alignment mode is centre at ultrawide.\n        if (g_aspectRatio >= WIDE_ASPECT_RATIO && Config::UIAlignmentMode == EUIAlignmentMode::Centre)\n            modifier.Flags &= ~CSD_CRI_LOGO;\n\n        // Clone CRI logo to move the technology\n        // logos to the true bottom right corner.\n        if ((modifier.Flags & CSD_CRI_LOGO) != 0)\n        {\n            auto v0 = getVertex(0);\n            auto v1 = getVertex(1);\n            auto v2 = getVertex(2);\n            auto v3 = getVertex(3);\n\n            auto originalTop = v0->Y;\n            auto originalHeight = v1->Y - v0->Y;\n\n            auto width = 422.0f;\n            auto height = 132.0f;\n            auto widthScaled = width * g_aspectRatioScale;\n            auto heightScaled = height * g_aspectRatioScale;\n\n            /////////////////////////////////////////////////////////////////////////\n            // Clone and scale white square to block the original technology logos //\n            /////////////////////////////////////////////////////////////////////////\n\n            auto top = originalTop + (originalHeight - heightScaled);\n            auto left = g_aspectRatio >= WIDE_ASPECT_RATIO ? 0.0f : vpWidth - widthScaled;\n\n            // Top Left\n            v0->X = left;\n            v0->Y = top;\n            v0->U = 0.0f;\n            v0->V = 0.01f;\n\n            // Bottom Left\n            v1->X = left;\n            v1->Y = vpHeight;\n            v1->U = 0.0f;\n            v1->V = 0.01f;\n\n            // Top Right\n            v2->X = vpWidth;\n            v2->Y = top;\n            v2->U = 0.0f;\n            v2->V = 0.01f;\n\n            // Bottom Right\n            v3->X = vpWidth;\n            v3->Y = vpHeight;\n            v3->U = 0.0f;\n            v3->V = 0.01f;\n\n            drawOriginal();\n\n            /////////////////////////////////////////////\n            // Clone and scale to fit technology logos //\n            /////////////////////////////////////////////\n\n            v0 = getVertex(0);\n            v1 = getVertex(1);\n            v2 = getVertex(2);\n            v3 = getVertex(3);\n\n            auto uv = PIXELS_TO_UV_COORDS(2048, 512, 858, 380, width, height);\n            auto& min = std::get<0>(uv);\n            auto& max = std::get<1>(uv);\n\n            // Top Left\n            v0->X = vpWidth - widthScaled;\n            v0->Y = vpHeight - heightScaled;\n            v0->U = min.x;\n            v0->V = min.y;\n\n            // Bottom Left\n            v1->X = vpWidth - widthScaled;\n            v1->Y = vpHeight;\n            v1->U = min.x;\n            v1->V = max.y;\n\n            // Top Right\n            v2->X = vpWidth;\n            v2->Y = vpHeight - heightScaled;\n            v2->U = max.x;\n            v2->V = min.y;\n\n            // Bottom Right\n            v3->X = vpWidth;\n            v3->Y = vpHeight;\n            v3->U = max.x;\n            v3->V = max.y;\n\n            drawOriginal();\n        }\n    }\n\n    if (g_aspectRatio > WIDE_ASPECT_RATIO)\n    {\n        if (isMainMenuPanels)\n            drawOriginal();\n\n        /////////////////////////////////////////////////\n        //  Scale metal borders to wide aspect ratios  //\n        /////////////////////////////////////////////////\n\n        if ((modifier.Flags & CSD_MAIN_MENU_PARTS_CAST_0222) != 0)\n        {\n            // Move cloned cast to left edge of existing cast.\n            for (size_t i = 0; i < r5.u32; i++)\n                getVertex(i)->X = getVertex(i)->X - width;\n\n            flipHorizontally();\n\n            // Extend to the left of the screen.\n            getVertex(2)->X = 0.0f;\n            getVertex(3)->X = 0.0f;\n\n            // Crop UVs to remove metal notch.\n            getVertex(0)->U = getVertex(0)->U + 0.0015f;\n            getVertex(1)->U = getVertex(1)->U + 0.0015f;\n            getVertex(2)->U = getVertex(2)->U + -0.1f;\n            getVertex(3)->U = getVertex(3)->U + -0.1f;\n\n            drawOriginal();\n        }\n\n        if ((modifier.Flags & CSD_MAIN_MENU_PARTS_CAST_0226) != 0)\n        {\n            // Move cloned cast to left edge of existing cast.\n            for (size_t i = 0; i < r5.u32; i++)\n                getVertex(i)->X = getVertex(i)->X - width;\n\n            flipHorizontally();\n\n            // Extend to the left of the screen.\n            getVertex(2)->X = 0.0f;\n            getVertex(3)->X = 0.0f;\n\n            // Crop UVs to remove metal dip.\n            getVertex(2)->U = getVertex(2)->U + -0.8f;\n            getVertex(3)->U = getVertex(3)->U + -0.8f;\n\n            drawOriginal();\n        }\n\n        if ((modifier.Flags & CSD_MAIN_MENU_PARTS_CAST_0227) != 0)\n        {\n            // Move cloned cast to right edge of existing cast.\n            for (size_t i = 0; i < r5.u32; i++)\n                getVertex(i)->X = getVertex(i)->X + width;\n\n            flipHorizontally();\n\n            // Extend to the right of the screen.\n            getVertex(0)->X = vpWidth;\n            getVertex(1)->X = vpWidth;\n\n            // Crop UVs to remove metal dip.\n            getVertex(0)->U = getVertex(0)->U + -0.8f;\n            getVertex(1)->U = getVertex(1)->U + -0.8f;\n\n            drawOriginal();\n        }\n    }\n    else if (g_aspectRatio < WIDE_ASPECT_RATIO)\n    {\n        /////////////////////////////////////////////////\n        // Scale metal borders to narrow aspect ratios //\n        /////////////////////////////////////////////////\n\n        if ((modifier.Flags & (CSD_MAIN_MENU_PARTS_CAST_0221 | CSD_MAIN_MENU_PARTS_CAST_0222)) != 0)\n        {\n            // Move vertices for UV adjustment.\n            getVertex(0)->Y = getVertex(0)->Y + 7.0f;\n            getVertex(2)->Y = getVertex(2)->Y + 7.0f;\n\n            // Crop top UVs to move panel top to vertex edge.\n            getVertex(0)->V = getVertex(0)->V + 0.00675f;\n            getVertex(2)->V = getVertex(2)->V + 0.00675f;\n\n            drawOriginal();\n\n            // Move cloned cast above existing cast.\n            for (size_t i = 0; i < r5.u32; i++)\n                getVertex(i)->Y = getVertex(i)->Y - height;\n\n            flipVertically();\n\n            // Crop top UVs to reduce pattern repetition.\n            getVertex(0)->V = getVertex(0)->V + 0.005f;\n            getVertex(2)->V = getVertex(2)->V + 0.005f;\n\n            drawOriginal();\n        }\n\n        if ((modifier.Flags & (CSD_MAIN_MENU_PARTS_CAST_0226 | CSD_MAIN_MENU_PARTS_CAST_0227)) != 0)\n        {\n            // Move vertices for UV adjustment.\n            getVertex(1)->Y = getVertex(1)->Y - 20.0f;\n            getVertex(3)->Y = getVertex(3)->Y - 20.0f;\n\n            // Crop bottom UVs to move panel bottom to vertex edge.\n            getVertex(1)->V = getVertex(1)->V + -0.0168;\n            getVertex(3)->V = getVertex(3)->V + -0.0168;\n\n            drawOriginal();\n\n            // Move cloned cast below existing cast.\n            for (size_t i = 0; i < r5.u32; i++)\n                getVertex(i)->Y = getVertex(i)->Y + height;\n\n            flipVertically();\n\n            // Crop bottom UVs to reduce pattern repetition.\n            getVertex(1)->V = getVertex(1)->V + -0.005f;\n            getVertex(3)->V = getVertex(3)->V + -0.005f;\n\n            drawOriginal();\n        }\n    }\n    else\n    {\n        if (isMainMenuPanels)\n            drawOriginal();\n    }\n\n    ctx.r1.u32 += size;\n}\n\n// Sonicteam::CPlatformMarathon::Draw\nPPC_FUNC_IMPL(__imp__sub_826315C8);\nPPC_FUNC(sub_826315C8)\n{\n    // r3 = Sonicteam::CPlatformMarathon*\n    // r4 = Vertex[r5]\n    // r5 = Vertex Count\n\n    Draw(ctx, base, __imp__sub_826315C8, 0x14);\n}\n\n// Sonicteam::CPlatformMarathon::DrawNoTex\nPPC_FUNC_IMPL(__imp__sub_82631718);\nPPC_FUNC(sub_82631718)\n{\n    // r3 = Sonicteam::CPlatformMarathon*\n    // r4 = Vertex[r5]\n    // r5 = Vertex Count\n\n    Draw(ctx, base, __imp__sub_82631718, 0x0C);\n}\n\n// Set CSD scale.\nPPC_FUNC_IMPL(__imp__sub_828C78E0);\nPPC_FUNC(sub_828C78E0)\n{\n    ctx.f1.f64 = 1280.0;\n    ctx.f2.f64 = 720.0;\n\n    __imp__sub_828C78E0(ctx, base);\n}\n\n// Sonicteam::EventEntityTask::Update\nPPC_FUNC_IMPL(__imp__sub_8264AC48);\nPPC_FUNC(sub_8264AC48)\n{\n    if (Config::CutsceneAspectRatio == ECutsceneAspectRatio::Original)\n        BlackBar::Show();\n\n    __imp__sub_8264AC48(ctx, base);\n}\n\n// Sonicteam::HUDResult::Update\nPPC_FUNC_IMPL(__imp__sub_824F4D80);\nPPC_FUNC(sub_824F4D80)\n{\n    BlackBar::Show();\n\n    __imp__sub_824F4D80(ctx, base);\n}\n\nPPC_FUNC_IMPL(__imp__sub_824D32C8);\nPPC_FUNC(sub_824D32C8)\n{\n    auto vftable = PPC_LOAD_U32(ctx.r3.u32);\n\n    // Sonicteam::HUDBattleResult\n    if (vftable == 0x820363E0)\n        BlackBar::Show();\n\n    __imp__sub_824D32C8(ctx, base);\n}\n\n// Sonicteam::EndingMode::Update\nPPC_FUNC_IMPL(__imp__sub_824A4A40);\nPPC_FUNC(sub_824A4A40)\n{\n    BlackBar::Show();\n\n    __imp__sub_824A4A40(ctx, base);\n}\n\n// Sonicteam::HUDLimitTime::ProcessMessage\nPPC_FUNC_IMPL(__imp__sub_824D6E50);\nPPC_FUNC(sub_824D6E50)\n{\n    if (g_aspectRatio < WIDE_ASPECT_RATIO && Config::UIAlignmentMode == EUIAlignmentMode::Centre)\n    {\n        __imp__sub_824D6E50(ctx, base);\n        return;\n    }\n\n    auto pHUDLimitTime = (Sonicteam::HUDLimitTime*)(base + ctx.r3.u32);\n\n    pHUDLimitTime->m_Y = 64.0f - (g_aspectRatioOffsetY / g_aspectRatioScale);\n\n    GuestToHostFunction<int>(sub_824D6D38, pHUDLimitTime, (double)pHUDLimitTime->m_X, (double)pHUDLimitTime->m_Y);\n\n    __imp__sub_824D6E50(ctx, base);\n}\n\n// Sonicteam::HUDRaderMap::Update\nPPC_FUNC_IMPL(__imp__sub_824F1538);\nPPC_FUNC(sub_824F1538)\n{\n    auto pHUDRaderMap = (Sonicteam::HUDRaderMap*)(base + ctx.r3.u32);\n\n    __imp__sub_824F1538(ctx, base);\n\n    pHUDRaderMap->m_pMaskTexture->m_Width = g_radarMapScale;\n    pHUDRaderMap->m_pMaskTexture->m_Height = g_radarMapScale;\n    pHUDRaderMap->m_X = g_radarMapX - g_radarMapCoverWidth / 2;\n    pHUDRaderMap->m_Y = g_radarMapY - g_radarMapCoverHeight / 2;\n}\n\n// Sonicteam::CObjBalloonIconDrawable::Draw\nPPC_FUNC_IMPL(__imp__sub_82352220);\nPPC_FUNC(sub_82352220)\n{\n    auto pCObjBalloonIconDrawable = (Sonicteam::CObjBalloonIconDrawable*)(base + ctx.r3.u32);\n    auto scale = g_aspectRatioScale;\n\n    if (g_aspectRatio > WIDE_ASPECT_RATIO)\n        scale = g_aspectRatio / WIDE_ASPECT_RATIO;\n\n    pCObjBalloonIconDrawable->m_aVertices[0].X = -1.0f / scale;\n    pCObjBalloonIconDrawable->m_aVertices[0].Y = 0.0f;\n    pCObjBalloonIconDrawable->m_aVertices[1].X = -1.0f / scale;\n    pCObjBalloonIconDrawable->m_aVertices[1].Y = g_aspectRatio / scale;\n    pCObjBalloonIconDrawable->m_aVertices[2].X = 1.0f / scale;\n    pCObjBalloonIconDrawable->m_aVertices[2].Y = 0.0f;\n    pCObjBalloonIconDrawable->m_aVertices[3].X = 1.0f / scale;\n    pCObjBalloonIconDrawable->m_aVertices[3].Y = g_aspectRatio / scale;\n\n    __imp__sub_82352220(ctx, base);\n}\n\n// Sonicteam::MovieObjectWmv::Draw\nPPC_FUNC_IMPL(__imp__sub_8264CC90);\nPPC_FUNC(sub_8264CC90)\n{\n    auto pMovieObjectWmv = (Sonicteam::MovieObjectWmv*)(base + ctx.r3.u32);\n\n    static XXH64_hash_t s_movieNameHash{};\n    static bool s_movieUsesCustomDimensions{};\n    static float s_movieLeft{};\n    static float s_movieRight{};\n    static float s_movieTop{};\n    static float s_movieBottom{};\n\n    auto movieNameHash = HashStr(pMovieObjectWmv->m_FilePath.c_str());\n\n    if (movieNameHash != s_movieNameHash)\n    {\n        s_movieNameHash = movieNameHash;\n        s_movieUsesCustomDimensions = pMovieObjectWmv->m_UseCustomDimensions;\n        s_movieLeft = pMovieObjectWmv->m_Left;\n        s_movieRight = pMovieObjectWmv->m_Right;\n        s_movieTop = pMovieObjectWmv->m_Top;\n        s_movieBottom = pMovieObjectWmv->m_Bottom;\n\n        LOGFN_UTILITY(\"Movie: {} - {}x{}\", pMovieObjectWmv->m_FilePath.c_str(), pMovieObjectWmv->m_Width.get(), pMovieObjectWmv->m_Height.get());\n    }\n\n    auto movieModifier = FindHash<MovieModifier>(g_movieModifiers, movieNameHash);\n\n    g_aspectRatioMovie = (float)pMovieObjectWmv->m_Width / (float)pMovieObjectWmv->m_Height;\n\n    float width, height, left, right, top, bottom;\n\n    if (s_movieUsesCustomDimensions)\n    {\n        auto movieRectLeft = s_movieLeft;\n        auto movieRectRight = s_movieRight;\n        auto movieRectTop = s_movieTop;\n        auto movieRectBottom = s_movieBottom;\n\n        if (g_aspectRatio > g_aspectRatioMovie)\n        {\n            // Scale width at wide aspect ratios.\n            movieRectLeft = s_movieLeft / (g_aspectRatio / g_aspectRatioMovie);\n            movieRectRight = s_movieRight / (g_aspectRatio / g_aspectRatioMovie);\n        }\n        else\n        {\n            // Scale height at narrow aspect ratios.\n            movieRectTop = s_movieTop / (g_aspectRatioMovie / g_aspectRatio);\n            movieRectBottom = s_movieBottom / (g_aspectRatioMovie / g_aspectRatio);\n        }\n\n        auto movieRectCentreX = (movieRectLeft + movieRectRight) / 2.0f;\n        auto movieRectCentreY = (movieRectTop + movieRectBottom) / 2.0f;\n\n        width = movieRectRight - movieRectLeft;\n        height = movieRectTop - movieRectBottom;\n\n        left = movieRectCentreX - (width / 2.0f);\n        right = movieRectCentreX + (width / 2.0f);\n        top = movieRectCentreY + (height / 2.0f);\n        bottom = movieRectCentreY - (height / 2.0f);\n    }\n    else\n    {\n        width = 2.0f;\n        height = 2.0f;\n\n        if (g_aspectRatio > g_aspectRatioMovie)\n        {\n            if ((movieModifier.Flags & MOVIE_CROP_WIDE) != 0)\n            {\n                // Crop vertically at wide aspect ratios.\n                height = 2.0f * (g_aspectRatio / g_aspectRatioMovie);\n            }\n            else\n            {\n                // Pillarbox at wide aspect ratios.\n                width = 2.0f * (g_aspectRatioMovie / g_aspectRatio);\n            }\n        }\n        else\n        {\n            if ((movieModifier.Flags & MOVIE_CROP_NARROW) != 0)\n            {\n                // Crop horizontally at narrow aspect ratios.\n                width = 2.0f * (g_aspectRatioMovie / g_aspectRatio);\n            }\n            else\n            {\n                // Letterbox at narrow aspect ratios.\n                height = 2.0f * (g_aspectRatio / g_aspectRatioMovie);\n            }\n        }\n\n        left = -width / 2.0f;\n        right = (width / 2.0f) * 2.0f;\n        top = height / 2.0f;\n        bottom = (-height / 2.0f) * 2.0f;\n    }\n\n    pMovieObjectWmv->m_UseCustomDimensions = true;\n    pMovieObjectWmv->m_Left = left;\n    pMovieObjectWmv->m_Right = right;\n    pMovieObjectWmv->m_Top = top;\n    pMovieObjectWmv->m_Bottom = bottom;\n\n    __imp__sub_8264CC90(ctx, base);\n}\n\nvoid ReplaceTextVariables(Sonicteam::TextEntity* pTextEntity)\n{\n    if (!pTextEntity || !pTextEntity->m_ImageVertexCount)\n        return;\n\n    auto variables = MapTextVariables(pTextEntity->m_pVariables);\n    auto variablesIndex = 0;\n    auto pictureIndex = 0;\n\n    for (int i = 0; i < pTextEntity->m_Text.size(); i++)\n    {\n        auto str = pTextEntity->m_Text.c_str();\n        auto c = ByteSwap(str[i]);\n\n        if (c != L'$')\n            continue;\n\n        // Some strings may have more placeholder\n        // characters than variables in the map.\n        if (variablesIndex >= variables.size())\n            break;\n\n        auto& variable = variables[variablesIndex];\n\n        if (variable.first == \"picture\")\n        {\n            auto isPlayStation = Config::ControllerIcons == EControllerIcons::PlayStation;\n\n            if (Config::ControllerIcons == EControllerIcons::Auto)\n                isPlayStation = hid::g_inputDeviceController == hid::EInputDevice::PlayStation;\n\n            auto& pftModifier = isPlayStation\n                ? g_buttonCropsPS3\n                : g_buttonCropsXenon;\n\n            auto hash = HashStr(variable.second);\n            auto baseParams = FindHash<ImGuiTextPictureCrop>(g_buttonCropsXenon, hash);\n            auto newParams = FindHash<ImGuiTextPictureCrop>(pftModifier, hash);\n\n            auto  uv = PIXELS_TO_UV_COORDS(g_fontPictureWidth, g_fontPictureHeight, newParams.X, newParams.Y, newParams.Width, newParams.Height);\n            auto& min = std::get<0>(uv);\n            auto& max = std::get<1>(uv);\n\n            auto  vi = pictureIndex * 6;\n            auto& v0 = pTextEntity->m_pImageVertices[vi + 0];\n            auto& v1 = pTextEntity->m_pImageVertices[vi + 1];\n            auto& v2 = pTextEntity->m_pImageVertices[vi + 2];\n            auto& v3 = pTextEntity->m_pImageVertices[vi + 3];\n            auto& v4 = pTextEntity->m_pImageVertices[vi + 4];\n            auto& v5 = pTextEntity->m_pImageVertices[vi + 5];\n\n            auto centreX = (v0.X + v2.X) / 2.0f;\n            auto widthAdjust = (float)newParams.Width / (float)baseParams.Width;\n            auto adjust = [&](auto& vertex)\n            {\n                vertex.X = centreX + (vertex.X - centreX) * widthAdjust;\n            };\n\n            // Bottom Left (Triangle 1)\n            adjust(v0);\n            v0.U = min.x;\n            v0.V = min.y;\n\n            // Top Left (Triangle 1)\n            adjust(v1);\n            v1.U = min.x;\n            v1.V = max.y;\n\n            // Top Right (Triangle 1)\n            adjust(v2);\n            v2.U = max.x;\n            v2.V = max.y;\n\n            // Bottom Left (Triangle 2)\n            adjust(v3);\n            v3.U = min.x;\n            v3.V = min.y;\n\n            // Top Right (Triangle 2)\n            adjust(v4);\n            v4.U = max.x;\n            v4.V = max.y;\n\n            // Bottom Right (Triangle 2)\n            adjust(v5);\n            v5.U = max.x;\n            v5.V = min.y;\n\n            pictureIndex++;\n        }\n\n        variablesIndex++;\n    }\n}\n\nvoid TextEntityAlloc(PPCRegister& r3)\n{\n    r3.u32 += sizeof(uint64_t);\n}\n\n// Sonicteam::TextEntity::TextEntity\nPPC_FUNC_IMPL(__imp__sub_8262DC60);\nPPC_FUNC(sub_8262DC60)\n{\n    auto pTextModifier = (uint64_t*)(base + ctx.r3.u32 + sizeof(Sonicteam::TextEntity));\n\n    // Default text modifier to scale.\n    *pTextModifier = CSD_SCALE;\n\n    __imp__sub_8262DC60(ctx, base);\n}\n\n// Sonicteam::TextEntity::Draw\nPPC_FUNC_IMPL(__imp__sub_8262D868);\nPPC_FUNC(sub_8262D868)\n{\n    auto pTextEntity = (Sonicteam::TextEntity*)(base + ctx.r3.u32);\n    auto textModifier = *(uint64_t*)(base + ctx.r3.u32 + sizeof(Sonicteam::TextEntity));\n\n    if ((textModifier & CSD_SKIP) != 0)\n        return;\n\n    auto x = pTextEntity->m_X;\n    auto y = pTextEntity->m_Y;\n    auto scaleX = pTextEntity->m_ScaleX;\n    auto scaleY = pTextEntity->m_ScaleY;\n\n    auto offsetX = 0.0f;\n    auto offsetY = 0.0f;\n    auto scale = g_aspectRatioScale;\n\n    if (Config::UIAlignmentMode == EUIAlignmentMode::Centre || BlackBar::IsVisible())\n    {\n        if (g_aspectRatio > WIDE_ASPECT_RATIO)\n        {\n            textModifier &= ~(CSD_ALIGN_LEFT | CSD_ALIGN_RIGHT);\n        }\n        else if (g_aspectRatio < WIDE_ASPECT_RATIO)\n        {\n            textModifier &= ~(CSD_ALIGN_TOP | CSD_ALIGN_BOTTOM);\n        }\n    }\n\n    if ((textModifier & CSD_ALIGN_RIGHT) != 0)\n        offsetX = g_aspectRatioOffsetX * 2.0f;\n    else if ((textModifier & CSD_ALIGN_LEFT) == 0)\n        offsetX = g_aspectRatioOffsetX;\n\n    if ((textModifier & CSD_ALIGN_BOTTOM) != 0)\n        offsetY = g_aspectRatioOffsetY * 2.0f;\n    else if ((textModifier & CSD_ALIGN_TOP) == 0)\n        offsetY = g_aspectRatioOffsetY;\n\n    if ((textModifier & CSD_SCALE) != 0)\n    {\n        scale *= g_aspectRatioGameplayScale;\n\n        if ((textModifier & CSD_ALIGN_RIGHT) != 0)\n            offsetX += 1280.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n        else if ((textModifier & CSD_ALIGN_LEFT) == 0)\n            offsetX += 640.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n\n        if ((textModifier & CSD_ALIGN_BOTTOM) != 0)\n            offsetY += 720.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n        else if ((textModifier & CSD_ALIGN_TOP) == 0)\n            offsetY += 360.0f * (1.0f - g_aspectRatioGameplayScale) * g_aspectRatioScale;\n    }\n\n    pTextEntity->m_X = offsetX + pTextEntity->m_X * scale;\n    pTextEntity->m_Y = offsetY + pTextEntity->m_Y * scale;\n    pTextEntity->m_ScaleX = scale;\n    pTextEntity->m_ScaleY = scale;\n\n    __imp__sub_8262D868(ctx, base);\n\n    pTextEntity->m_X = x;\n    pTextEntity->m_Y = y;\n    pTextEntity->m_ScaleX = scaleX;\n    pTextEntity->m_ScaleY = scaleY;\n\n    ReplaceTextVariables(pTextEntity);\n}\n\nvoid SetTextEntityModifier(Sonicteam::TextEntity* pTextEntity, uint64_t flags)\n{\n    if (!pTextEntity)\n        return;\n\n    auto pTextModifier = (uint64_t*)(reinterpret_cast<uint8_t*>(pTextEntity) + sizeof(Sonicteam::TextEntity));\n\n    *pTextModifier = flags;\n\n    pTextEntity->m_FieldDD = true;\n    pTextEntity->Update();\n}\n\n// Sonicteam::HUDMainMenu::Destroy\nPPC_FUNC_IMPL(__imp__sub_824E2978);\nPPC_FUNC(sub_824E2978)\n{\n    g_bgArrows.clear();\n    g_fgArrows.clear();\n\n    __imp__sub_824E2978(ctx, base);\n}\n\n// Sonicteam::HUDMainMenu::Update\nPPC_FUNC_IMPL(__imp__sub_824E11D0);\nPPC_FUNC(sub_824E11D0)\n{\n    auto pHUDMainMenu = (Sonicteam::HUDMainMenu*)(base + ctx.r3.u32 - 8);\n    auto pHudTextRoot = pHUDMainMenu->m_pHudTextRoot;\n\n    static bool s_preservedTextParams{};\n    static float s_multiplayerTextOffsetY{};\n    static float s_tagTextOffsetY{};\n    static float s_battleTextOffsetY{};\n\n    static constexpr double HIDE_TEXT_OFFSET = -100000.0f;\n\n    auto isTrialSelect = MainMenuTaskPatches::s_state >= 12 && MainMenuTaskPatches::s_state <= 15;\n\n    auto isTag = MainMenuTaskPatches::s_state == Sonicteam::MainMenuTask::MainMenuState_Tag ||\n                 MainMenuTaskPatches::s_state == Sonicteam::MainMenuTask::MainMenuState_Tag1PSelect ||\n                 MainMenuTaskPatches::s_state == Sonicteam::MainMenuTask::MainMenuState_ExitToStage;\n\n    while (pHudTextRoot)\n    {\n        if (pHudTextRoot->m_SceneName == \"main_menu\")\n        {\n            if (pHudTextRoot->m_CastName == \"multi_player\")\n            {\n                if (!s_preservedTextParams)\n                    s_multiplayerTextOffsetY = pHudTextRoot->m_OffsetY;\n\n                pHudTextRoot->m_OffsetY = isTrialSelect ? HIDE_TEXT_OFFSET : s_multiplayerTextOffsetY;\n            }\n            else if (pHudTextRoot->m_CastName == \"tag\")\n            {\n                if (!s_preservedTextParams)\n                    s_tagTextOffsetY = pHudTextRoot->m_OffsetY;\n\n                pHudTextRoot->m_OffsetY = (isTrialSelect || isTag) ? HIDE_TEXT_OFFSET : s_tagTextOffsetY;\n            }\n            else if (pHudTextRoot->m_CastName == \"battle\")\n            {\n                if (!s_preservedTextParams)\n                    s_battleTextOffsetY = pHudTextRoot->m_OffsetY;\n\n                pHudTextRoot->m_OffsetY = isTrialSelect ? HIDE_TEXT_OFFSET : s_battleTextOffsetY;\n            }\n        }\n\n        pHudTextRoot = pHudTextRoot->m_pNext;\n    }\n\n    s_preservedTextParams = true;\n\n    // Draw faded letterbox at tall aspect ratios.\n    if (!OptionsMenu::s_isVisible && g_aspectRatio < NARROW_ASPECT_RATIO)\n    {\n        BlackBar::Show(true);\n        BlackBar::SetBorderMargin(Scale(BlackBar::ms_MenuBorderMargin, true));\n    }\n\n    __imp__sub_824E11D0(ctx, base);\n}\n\n// Sonicteam::HUDMainDisplay::Update\nPPC_FUNC_IMPL(__imp__sub_824DCF40);\nPPC_FUNC(sub_824DCF40)\n{\n    auto pHUDMainDisplay = (Sonicteam::HUDMainDisplay*)(base + ctx.r3.u32);\n\n    SetTextEntityModifier(pHUDMainDisplay->m_Field184.get(), CSD_ALIGN_RIGHT | CSD_SCALE);\n    SetTextEntityModifier(pHUDMainDisplay->m_spTrickPointText.get(), CSD_ALIGN_RIGHT | CSD_SCALE);\n    SetTextEntityModifier(pHUDMainDisplay->m_Field194.get(), CSD_ALIGN_RIGHT | CSD_SCALE);\n    SetTextEntityModifier(pHUDMainDisplay->m_spSavePointTimeText.get(), CSD_ALIGN_BOTTOM | CSD_SCALE);\n\n    __imp__sub_824DCF40(ctx, base);\n}\n\n// Sonicteam::HintWindowTask::Update\nPPC_FUNC_IMPL(__imp__sub_824D12F0);\nPPC_FUNC(sub_824D12F0)\n{\n    auto pHintWindowTask = (Sonicteam::HintWindowTask*)(base + ctx.r3.u32);\n\n    SetTextEntityModifier(pHintWindowTask->m_Field8C.get(), CSD_ALIGN_BOTTOM | CSD_SCALE);\n    SetTextEntityModifier(pHintWindowTask->m_Field94.get(), CSD_ALIGN_BOTTOM | CSD_SCALE);\n\n    __imp__sub_824D12F0(ctx, base);\n}\n\n// Sonicteam::MessageWindowTask::Update\nPPC_FUNC_IMPL(__imp__sub_82507E68);\nPPC_FUNC(sub_82507E68)\n{\n    auto pMessageWindowTask = (Sonicteam::MessageWindowTask*)(base + ctx.r3.u32);\n\n    SetTextEntityModifier(pMessageWindowTask->m_Field90.get(), CSD_ALIGN_BOTTOM | CSD_SCALE);\n    SetTextEntityModifier(pMessageWindowTask->m_Field98.get(), CSD_ALIGN_BOTTOM | CSD_SCALE);\n\n    __imp__sub_82507E68(ctx, base);\n}\n\n// Sonicteam::TextFontPicture::LoadResource\nPPC_FUNC_IMPL(__imp__sub_8263CC40);\nPPC_FUNC(sub_8263CC40)\n{\n    auto pTextFontPicture = (Sonicteam::TextFontPicture*)(base + ctx.r3.u32);\n\n    __imp__sub_8263CC40(ctx, base);\n\n    g_fontPictureWidth = pTextFontPicture->m_TextureWidth;\n    g_fontPictureHeight = pTextFontPicture->m_TextureHeight;\n}\n\n// -------------- CSD MODIFIERS --------------- //\n\nconst xxHashMap<CsdModifier> g_csdModifiers =\n{\n    // audio\n    { HashStr(\"sprite/audio/audio/pod/pod\"), { CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/pod/pod/Cast_1084\"), { CSD_POD_BASE | CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/audio/audio/pod/pod/Cast_1085\"), { CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/pod/pod/Cast_1086\"), { CSD_POD_CLONE | CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/pod/pod/Cast_1087\"), { CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/pod/pod/Cast_1088\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/audio/audio/genre\"), { CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/genre_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/audio_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/audio/audio/audio_cursor2\"), { CSD_SCALE } },\n\n    // background\n    { HashStr(\"sprite/background/background/mainmenu_back\"), { CSD_MODIFIER_ULTRAWIDE_ONLY | CSD_STRETCH_HORIZONTAL | CSD_PROHIBIT_BLACK_BAR } },\n    { HashStr(\"sprite/background/background/tag\"), { CSD_SCALE } },\n    { HashStr(\"sprite/background/background/movie\"), { CSD_SCALE } },\n    { HashStr(\"sprite/background/background/main_menu\"), { CSD_SCENE_DISABLE_MOTION | CSD_MODIFIER_ULTRAWIDE_ONLY } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu1/yaji1\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu1/yaji2\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu1/yaji3\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu1/yaji4\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu1/yaji5\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu1/yaji6\"), { CSD_CHEVRON | CSD_SCALE | CSD_ALIGN_RIGHT | CSD_REPEAT_LEFT } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji7\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji8\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji9\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji10\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji11\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji12\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji13\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji14\"), { CSD_CHEVRON | CSD_SCALE | CSD_SKIP } },\n    { HashStr(\"sprite/background/background/main_menu/main_menu2/yaji15\"), { CSD_CHEVRON | CSD_SCALE | CSD_ALIGN_LEFT | CSD_REPEAT_RIGHT } },\n    { HashStr(\"sprite/background/background/cd\"), { CSD_SCALE } },\n    { HashStr(\"sprite/background/background/battle\"), { CSD_SCALE } },\n    { HashStr(\"sprite/background/background/fileselect\"), { CSD_SCALE } },\n\n    // battledisplay_1p\n    { HashStr(\"sprite/battledisplay_1p/power\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/power_a\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/power_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/itembox_01\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/ring\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/ring_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/enemy\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/bronze_medal\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/gold_medal\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/bar_ue\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/power_bar_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/ring_000_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/custom_gem\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/custom_level1\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_1p/custom_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n\n    // battledisplay_2p\n    { HashStr(\"sprite/battledisplay_2p/power\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/power_a\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/power_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/itembox_01\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/ring\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/ring_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/enemy\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/bronze_medal\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/gold_medal\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/bar_ue\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/power_bar_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/ring_000_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/custom_gem\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/custom_level1\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/battledisplay_2p/custom_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n\n    // battle_result\n    { HashStr(\"sprite/battle_result/battle_result/congratulations\"), { CSD_ALIGN_TOP } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_ue/plate1\"), { CSD_ALIGN_TOP | CSD_EXTEND_LEFT } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_ue/plate2\"), { CSD_ALIGN_TOP } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_ue/plate3\"), { CSD_ALIGN_TOP } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_ue/plate4\"), { CSD_ALIGN_TOP | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_sita/plate5\"), { CSD_ALIGN_BOTTOM | CSD_EXTEND_LEFT } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_sita/plate6\"), { CSD_ALIGN_BOTTOM } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_sita/plate7\"), { CSD_ALIGN_BOTTOM } },\n    { HashStr(\"sprite/battle_result/battle_result/plate/plate_sita/plate8\"), { CSD_ALIGN_BOTTOM | CSD_EXTEND_RIGHT } },\n\n    // black_out\n    { HashStr(\"sprite/black_out/black_out\"), { CSD_STRETCH } },\n\n    // bossname\n    { HashStr(\"sprite/bossname/egg_cerberus/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1071/egg_cerberus/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1081/egg_cerberus/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/egg_genesis/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1091/egg_genesis/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/egg_wyvern/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1111/egg_wyvern/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/egg_wyvern/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1111/egg_wyvern/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/iblis/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1001/iblis/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1011/iblis/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1031/iblis/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/mephiles/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1041/mephiles/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1061/mephiles/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/shadow_the_hedgehog/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1161/shadow_the_hedgehog/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/silver_the_hedgehog/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1151/silver_the_hedgehog/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/solaris/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1121/solaris/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/bossname/sonic_the_hedgehog/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"event/e1141/sonic_the_hedgehog/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n\n    // button_window\n    { HashStr(\"sprite/button_window/button_window/Scene_0000/Null_0000/Cast_0001\"), { CSD_BUTTON_WINDOW | CSD_SCALE } },\n    { HashStr(\"sprite/button_window/button_window/Scene_0000/Null_0000/Cast_0002\"), { CSD_BUTTON_WINDOW | CSD_SCALE | CSD_EXTEND_RIGHT } },\n\n    // cri_logo\n    { HashStr(\"sprite/logo/cri_logo/Scene_0000/Null_0002/bg\"), { CSD_STRETCH } },\n    { HashStr(\"sprite/logo/cri_logo/Scene_0000/Null_0002/criware\"), { CSD_CRI_LOGO | CSD_SCALE } },\n\n    // gadget_ber\n    { HashStr(\"sprite/gadget_ber/gadget_bar/gadgetbar\"), { CSD_ALIGN_BOTTOM_RIGHT } },\n    { HashStr(\"sprite/gadget_ber/gadget_bar/gadgetbar_anime\"), { CSD_ALIGN_BOTTOM_RIGHT } },\n    { HashStr(\"sprite/gadget_ber/gadget_bar/gadgetbar_ue\"), { CSD_ALIGN_BOTTOM_RIGHT } },\n    { HashStr(\"sprite/gadget_ber/gadget_bar/icon_text\"), { CSD_ALIGN_BOTTOM_RIGHT } },\n\n    // goldmedal\n    { HashStr(\"sprite/goldmedal/goldmedal/ranking\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/goldmedal\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/charaselect_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/charaselect_cursor2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/board_cursor_sita\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/board_cursor_ue\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/total_medal\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/sonic\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/shadow\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/silver\"), { CSD_SCALE } },\n    { HashStr(\"sprite/goldmedal/goldmedal/last\"), { CSD_SCALE } },\n\n    // tips_sh_x\n    { HashStr(\"sprite/interlude/tips_x/tips_sh_x/Scene_0000\"), { CSD_SCALE } },\n\n    // tips_si_x\n    { HashStr(\"sprite/interlude/tips_x/tips_si_x/Scene_0000\"), { CSD_SCALE } },\n\n    // tips_so_x\n    { HashStr(\"sprite/interlude/tips_x/tips_so_x/Scene_0000\"), { CSD_SCALE } },\n\n    // loading\n    { HashStr(\"sprite/loading/loading/Scene_0000/Loading\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } },\n    { HashStr(\"sprite/loading/loading/Scene_0000/Loading_02\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } },\n    { HashStr(\"sprite/loading/loading/Scene_0000/arrow_01\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } },\n    { HashStr(\"sprite/loading/loading/Scene_0000/arrow_02\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } },\n    { HashStr(\"sprite/loading/loading/Scene_0000/arrow_03\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } },\n\n    // maindisplay\n    { HashStr(\"sprite/maindisplay/power\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/custom_bar_anime\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/power_a\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/power_bar_anime\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/itembox_01\"), { CSD_ALIGN_BOTTOM | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/score\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/life_ber_anime\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/life\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/time\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/ring\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/ring_anime\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/bar_ue\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/power_bar_effect\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/ring_000_effect\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/boss_gauge\"), { CSD_ALIGN_TOP_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/boss_gauge_anime\"), { CSD_ALIGN_TOP_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/item_ber_anime\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/item\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/custom_gem\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/maindisplay/custom_level\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n\n    // main_menu\n    { HashStr(\"sprite/main_menu/main_menu_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/main_menu_cursor2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/main_menu_cursor3\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/eposodeselect\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/episodeselect_cursor1\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/episodeselect_cursor2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/Null_1074/sita3\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/Null_1074/sita5\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/Null_1074/sita6\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/Null_1074/sita7\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/Null_1074/sita8\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/Null_1074/sita9\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/goldmedal\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/zanki_3\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/zanki_2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/zanki\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/savedate/savedata/ring\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/character_choice\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/character_cursor1\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/character_cursor2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/stage_plate/stage_plate\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/stage_plate/stage_plate/stage_plate2\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/stage_plate/stage_plate/Cast_1337\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/stage_plate/stage_plate/Cast_1338\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/stage_plate/stage_plate/Cast_1339\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/stage_plate/stage_plate/Cast_1340\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/mission_plate/mission_plate\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/mission_plate/mission_plate/mission_plate2\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/mission_plate/mission_plate/Cast_1332\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/mission_plate/mission_plate/Cast_1334\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/mission_plate/mission_plate/Cast_1335\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/mission_plate/mission_plate/Cast_1336\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/stage_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/mission_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/stage_cursor2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/text\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/text_cover/Null_0290/cover_l\"), { CSD_SCALE | CSD_UV_MODIFIER | CSD_REPEAT_LEFT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0.0015f, 0, 0.0015f, 0, 0, 0, 0, 0 }, {}, { 0, 0, 0, 0, -0.05f, 0, -0.05f, 0 } } },\n    { HashStr(\"sprite/main_menu/text_cover/Null_0290/cover_c\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/text_cover/Null_0290/vocer_r\"), { CSD_SCALE | CSD_UV_MODIFIER | CSD_REPEAT_RIGHT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, 0.0015f, 0, 0.0015f, 0 }, {}, { -0.05f, 0, -0.05f, 0, 0, 0, 0, 0 } } },\n    { HashStr(\"sprite/main_menu/mission_text/rank\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/mission_text/item_icon\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/mission_text/m_ring_icon\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0218/Cast_0221\"), { CSD_MAIN_MENU_PARTS_CAST_0221 | CSD_SCALE | CSD_EXTEND_RIGHT, { 0, 0.005f, 0, 0, 0, 0.005f, 0, 0 }, {}, { 0, 0, 0, -0.0175f, 0, 0, 0, -0.0175f } } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0218/Cast_0222\"), { CSD_MAIN_MENU_PARTS_CAST_0222 | CSD_SCALE | CSD_UV_MODIFIER, { 0.0015f, 0, 0.0015f, 0, 0, 0, 0, 0 } } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0960/Cast_0964\"), { CSD_SCALE | CSD_EXTEND_RIGHT } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0960/Cast_0965\"), { CSD_SCALE | CSD_REPEAT_LEFT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, {}, {}, { 0, 0, 0, 0, -0.5f, 0, -0.5f, 0 } } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0960/Cast_0966\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0960/Cast_0966/Cast_0967\"), { CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0224/Cast_0226\"), { CSD_MAIN_MENU_PARTS_CAST_0226 | CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/main_menu_parts/Null_0224/Cast_0227\"), { CSD_MAIN_MENU_PARTS_CAST_0227 | CSD_SCALE } },\n    { HashStr(\"sprite/main_menu/titlebar_effect\"), { CSD_SCALE } },\n\n    // map_twn\n    { HashStr(\"sprite/interlude/map_twn/map_twn/Scene_0000\"), { CSD_SCALE } },\n\n    // pausemenu\n    { HashStr(\"sprite/pausemenu/pausemenu/pause_menu\"), { CSD_SCALE } },\n    { HashStr(\"sprite/pausemenu/pausemenu/pause_menu_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/pausemenu/pausemenu/mission\"), { CSD_SCALE } },\n\n    // radarmap_cover\n    { HashStr(\"sprite/radarmap_cover/radarmap_cover/Scene_0000\"), { CSD_RADARMAP | CSD_ALIGN_TOP_RIGHT | CSD_SCALE } },\n\n    // result\n    { HashStr(\"sprite/result/result/title_plate/plate_ue\"), { CSD_ALIGN_TOP | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/title_plate/plate_sita\"), { CSD_ALIGN_BOTTOM | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/title_plate/result_title/result_title_ob\"), { CSD_ALIGN_TOP | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/score/score_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/score/score_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/score/score_plate/score_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/score/score_plate/score_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time/time_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time/time_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time/time_plate/time_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time/time_plate/time_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring/ring_plare_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring/ring_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring/ring_plate/ring_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring/ring_plate/ring_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time_bonus/time_bonus_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time_bonus/time_bonus_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time_bonus/time_bonus_plate/timebonus_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/time_bonus/time_bonus_plate/time_bonus_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring_bonus/ring_bonus_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring_bonus/ring_bonus_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring_bonus/ring_bonus_plate/ringbonus_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/ring_bonus/ring_bonus_plate/ring_bonus_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/amiplate/ami_plate_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/result/parts_anime_result/amiplate/ami_plate\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/town_score_t/score_plate_kage_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/town_score_t/score_plate_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/town_score_t/score_plate_t/score_text_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/town_score_t/score_plate_t/score_plate_title_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_t/time_plate_kage_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_t/time_plate_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_t/time_plate_t/time_text_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_t/time_plate_t/time_plate_title_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_t/ring_plare_kage_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_t/ring_plate_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_t/ring_plate_t/ring_text_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_t/ring_plate_t/ring_title_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_bonus_t/time_bonus_kage_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_bonus_t/time_bonus_plate_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_bonus_t/time_bonus_plate_t/timebonus_text_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/time_bonus_t/time_bonus_plate_t/time_bonus_plate_title_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_bonus_t/ring_bonus_plate_kage_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_bonus_t/ring_bonus_plate_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_bonus_t/ring_bonus_plate_t/ringbonus_text_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/ring_bonus_t/ring_bonus_plate_t/ring_bonus_plate_title_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/record_t/record_plate_kage_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/record_t/record_bonus_plate_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/record_t/record_bonus_plate_t/record_text_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/record_t/record_bonus_plate_t/record_plate_title_t\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/record_t/item_icon\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/amiplate_t\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate_kage01\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/list_text01\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/list_text01/nyoro01\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/ring01\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/ring01/ring01_b\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/bonus_text01\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/rank_list_s_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_s/list_plate01/rank_list_s\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate_kage02\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/list_text02\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/list_text02/nyoro02\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/ring02\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/ring02/ring02_b\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/bonus_text02\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/rank_list_a_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_a/list_plate02/rank_list_a\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate_kage03\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/list_text03\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/list_text03/nyoro03\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/ring03\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/ring03/ring03_b\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/bonus_text03\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/rank_list_b_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_b/list_plate03/rank_list_b\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate_kage04\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate04\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate04/list_text04\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate04/list_text04/nyoro04\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate04/ring04\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate04/ring04/ring04_b\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/list_plate04/bonus_text04\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/rank_list_c_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_c/rank_list_c\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate_kage05\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/list_text05\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/list_text05/nyoro05\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/ring05\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/ring05/ring05_b\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/bonus_text05\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/rank_list_d_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/bonus_list/rank_d/list_plate05/rank_list_d\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/amiplate/ami_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/amiplate/ami_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/rank_bonus/rank_bonus_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/rank_bonus/rank_bonus_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/rank_bonus/rank_bonus_plate/rank_bonus_plate_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/rank_bonus/rank_bonus_plate/rank_bonus_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/rank_bonus/rank_bonus_ring\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/rank_bonus/rank_bonus_ring/rank_bonus_ring_b\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/total_ring/total_ring_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/total_ring/total_ring_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/total_ring/total_ring_plate/total_ring_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/TotalRing_Goldfont/total_ring_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/TotalRing_Goldfont/ring_l\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/TotalRing_Goldfont/ring_l_b\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/total_score/total_score_plate_kage\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/total_score/total_score_plate\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/total_score/total_score_plate/totalscore_text\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/total_score/total_score_plate/total_score_plate_title\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/rank/rank_plate_kage\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/rank/rank_plate\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/totalscore_rank/parts_anime/rank/rank_plate/rank_plate_title\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/rank_anime\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/score_newrecord\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/time_newrecord\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/ring_newrecord\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/timebonus_newrecord\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/result/result/ringbonus_newrecord\"), { CSD_ALIGN_LEFT | CSD_SCALE } },\n\n    // result WIP ultrawide adjustments\n    // { HashStr(\"sprite/result/result/title_plate/plate_ue/plate_ue_kage_l\"), { CSD_ALIGN_TOP | CSD_SCALE | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_LEFT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0.015f, 0, 0.015f, 0, 0, 0, 0, 0 }, {}, { 0, 0, 0, 0, -0.6f, 0, -0.6f, 0 } } },\n    // { HashStr(\"sprite/result/result/title_plate/plate_ue/plate_ue_kage_r\"), { CSD_ALIGN_TOP | CSD_SCALE | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_RIGHT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, 0.015f, 0, 0.015f, 0 }, {}, { -0.6f, 0, -0.6f, 0, 0, 0, 0, 0 } } },\n    // { HashStr(\"sprite/result/result/title_plate/plate_ue/plate_ue_plate_l\"), { CSD_ALIGN_TOP | CSD_SCALE | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_LEFT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0.015f, 0, 0.015f, 0, 0, 0, 0, 0 }, {}, { 0, 0, 0, 0, -0.6f, 0, -0.6f, 0 } } },\n    // { HashStr(\"sprite/result/result/title_plate/plate_ue/plate_ue_plate_r\"), { CSD_ALIGN_TOP | CSD_SCALE | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_RIGHT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, -0.015f, 0, -0.015f, 0 }, {}, { 0.6f, 0, 0.6f, 0, 0, 0, 0, 0 } } },\n    // { HashStr(\"sprite/result/result/title_plate/plate_sita/plate_sita_kage_l\"), { CSD_ALIGN_BOTTOM | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_RIGHT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, -0.08f, 0, -0.08f, 0 }, {}, { 0.6f, 0, 0.6f, 0, 0, 0, 0, 0 } } },    // TODO: this crashes the game.\n    // { HashStr(\"sprite/result/result/title_plate/plate_sita/plate_sita_kage_r\"), { CSD_ALIGN_BOTTOM | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_LEFT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0.015f, 0, 0.015f, 0, 0, 0, 0, 0 }, {}, { 0, 0, 0, 0, -0.6f, 0, -0.6f, 0 } } },   // TODO: this crashes the game.\n    // { HashStr(\"sprite/result/result/title_plate/plate_sita/plate_sita_plate_l\"), { CSD_ALIGN_BOTTOM | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_RIGHT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, -0.015f, 0, -0.015f, 0 }, {}, { 0.6f, 0, 0.6f, 0, 0, 0, 0, 0 } } }, // TODO: this crashes the game.\n    // { HashStr(\"sprite/result/result/title_plate/plate_sita/plate_sita_plate_r\"), { CSD_ALIGN_BOTTOM | CSD_REPEAT_FLIP_HORIZONTAL | CSD_UV_MODIFIER | CSD_REPEAT_LEFT | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER, { 0.015f, 0, 0.015f, 0, 0, 0, 0, 0 }, {}, { 0, 0, 0, 0, -0.6f, 0, -0.6f, 0 } } },  // TODO: this crashes the game.\n    // { HashStr(\"sprite/result/result/result/parts_anime_result/amiplate/ami_plate\"), { CSD_REPEAT_RIGHT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_FLIP_VERTICAL | CSD_UV_MODIFIER | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, -0.025f, 0, -0.025f, 0 }, {}, { -0.01285f, 0.002f, -0.01285f, 0.002f, -0.01285f, 0.002f, -0.01285f, 0.002f } } },\n    // { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/amiplate_t/ami_plate_t\"), { CSD_REPEAT_RIGHT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_FLIP_VERTICAL | CSD_UV_MODIFIER | CSD_REPEAT_UV_MODIFIER, { 0, 0, 0, 0, -0.025f, 0, -0.025f, 0 }, {}, { -0.01285f, 0.002f, -0.01285f, 0.002f, -0.01285f, 0.002f, -0.01285f, 0.002f } } },\n    // { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/amiplate/ami_plate\"), { CSD_REPEAT_LEFT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_FLIP_VERTICAL | CSD_UV_MODIFIER | CSD_REPEAT_UV_MODIFIER, { 0.025f, 0, 0.025f, 0, 0, 0, 0, 0 }, {}, { 0.027f, 0.002f, 0.027f, 0.002f, 0.027f, 0.002f, 0.027f, 0.002f } } },\n\n    // result amiplate fade (needs special case for 16:9 or narrower)\n    // { HashStr(\"sprite/result/result/result/parts_anime_result/amiplate/ami_plate\"), { CSD_COLOUR_MODIFIER, {}, { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFF00 }, } },\n    // { HashStr(\"sprite/result/result/town_mission/parts_anime_result_t/amiplate_t/ami_plate_t\"), { CSD_COLOUR_MODIFIER, {}, { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0xFFFFFF00 } } },\n    // { HashStr(\"sprite/result/result/bonus/parts_anime_bonus/amiplate/ami_plate\"), { CSD_COLOUR_MODIFIER, {}, { 0xFFFFFF00, 0xFFFFFF00, 0xFFFFFFFF, 0xFFFFFFFF } } },\n\n    // sonicteam_logo\n    { HashStr(\"sprite/logo/sonicteam_logo/sonicteam\"), { CSD_SCALE } },\n\n    // stagetitle\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_aqa/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_cmn/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_csc/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_dtd/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_end/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_flc/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_kdv/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_rct/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_tpj/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_twn_a/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_twn_b/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_twn_c/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_wap/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/interlude/stagetitle/stage_title_wvo/Scene_0000\"), { CSD_SCALE } },\n\n    // tag_character\n    { HashStr(\"sprite/tag_character/tag_character/1p_tug/1p_tug/1p_tug1\"), { CSD_SCALE | CSD_REPEAT_LEFT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER | CSD_REPEAT_COLOUR_MODIFIER, {}, {}, { 0, 0, 0, 0, -0.5f, 0, -0.5f, 0 }, { 0xFFFFFF50, 0xFFFFFF50, 0xFFFFFF50, 0xFFFFFF50 } } },\n    { HashStr(\"sprite/tag_character/tag_character/1p_tug/1p_tug/1p_tug2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/2p_tug/2p_tug/2p_tug1\"), { CSD_SCALE | CSD_REPEAT_RIGHT | CSD_REPEAT_FLIP_HORIZONTAL | CSD_REPEAT_EXTEND | CSD_REPEAT_UV_MODIFIER | CSD_REPEAT_COLOUR_MODIFIER, {}, {}, { 0.5f, 0, 0.5f, 0, 0, 0, 0, 0 }, { 0xFFFFFF50, 0xFFFFFF50, 0xFFFFFF50, 0xFFFFFF50 } } },\n    { HashStr(\"sprite/tag_character/tag_character/2p_tug/2p_tug/2p_tug2\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/1p_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/2p_cursor\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/1p_name\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/2p_name\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/stage_window\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/stage\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/controller_1p\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/controller_2p\"), { CSD_SCALE } },\n    { HashStr(\"sprite/tag_character/tag_character/entry\"), { CSD_SCALE } },\n\n    // tagdisplay_1p\n    { HashStr(\"sprite/tagdisplay_1p/power\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/power_a\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/power_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/itembox_01\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/score\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/time\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/ring\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/ring_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/bar_ue\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/life_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/life\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/item_ber_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/item\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/power_bar_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/ring_000_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/custom_gem\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/custom_level1\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_1p/custom_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n\n    // tagdisplay_2p\n    { HashStr(\"sprite/tagdisplay_2p/power\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/power_a\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/power_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/itembox_01\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/score\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/time\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/ring\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/ring_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/bar_ue\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/life_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/life\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/item_ber_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/item\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/power_bar_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/ring_000_effect\"), { CSD_MULTIPLAYER | CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/custom_gem\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/custom_level1\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/tagdisplay_2p/custom_bar_anime\"), { CSD_MULTIPLAYER | CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE } },\n\n    // talkwindow\n    { HashStr(\"sprite/talkwindow/talkwindow/window\"), { CSD_ALIGN_BOTTOM | CSD_SCALE } },\n    { HashStr(\"sprite/talkwindow/talkwindow/nafuda\"), { CSD_ALIGN_BOTTOM | CSD_SCALE } },\n    { HashStr(\"sprite/talkwindow/talkwindow/Scene_0021\"), { CSD_ALIGN_BOTTOM | CSD_SCALE } },\n\n    // title\n    { HashStr(\"sprite/title/title/Scene_Title/Logo_add\"), { CSD_SCALE } },\n    { HashStr(\"sprite/title/title/Scene_Title/Logo\"), { CSD_SCALE } },\n    { HashStr(\"sprite/title/title/Scene_Title/copyright\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_MODIFIER_NARROW_ONLY } },\n\n    // titleloop_sth\n    { HashStr(\"sprite/logo/titleloop_sth/Scene_0000\"), { CSD_ALIGN_BOTTOM_RIGHT | CSD_SCALE | CSD_MOVIE | CSD_MODIFIER_NARROW_ONLY } },\n\n    // towndisplay\n    { HashStr(\"sprite/towndisplay/ring\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n    { HashStr(\"sprite/towndisplay/ring_anime\"), { CSD_ALIGN_TOP_LEFT | CSD_SCALE } },\n\n    // trickpoint\n    { HashStr(\"sprite/trickpoint/trickpoint/Scene_0000\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/trickpoint/trickpoint/Scene_0001\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n    { HashStr(\"sprite/trickpoint/trickpoint/score\"), { CSD_ALIGN_RIGHT | CSD_SCALE } },\n\n    // windowtest\n    { HashStr(\"sprite/windowtest/Scene_0000\"), { CSD_SCALE } },\n    { HashStr(\"sprite/windowtest/okuribotan\"), { CSD_SCALE } },\n    { HashStr(\"sprite/windowtest/cursor\"), { CSD_SCALE } },\n};\n\nstd::optional<CsdModifier> FindCsdModifier(uint32_t data)\n{\n    XXH64_hash_t path;\n    {\n        std::lock_guard lock(g_pathMutex);\n\n        auto findResult = g_paths.find(g_memory.Translate(data));\n\n        if (findResult == g_paths.end())\n            return {};\n\n        path = findResult->second;\n    }\n\n    auto findResult = g_csdModifiers.find(path);\n\n    if (findResult != g_csdModifiers.end())\n        return findResult->second;\n\n    return {};\n}\n\n// ------------- MOVIE MODIFIERS -------------- //\n\nconst xxHashMap<MovieModifier> g_movieModifiers =\n{\n    { HashStr(\"sound\\\\title_loop_GBn.wmv\"), { MOVIE_CROP_NARROW } }\n};\n\n"
  },
  {
    "path": "MarathonRecomp/patches/aspect_ratio_patches.h",
    "content": "#pragma once\n\n#include <xxHashMap.h>\n\n#define MAKE_BITFLAG32(bit) 1U << bit\n#define MAKE_BITFLAG64(bit) 1ULL << bit\n\ninline constexpr float NARROW_ASPECT_RATIO = 4.0f / 3.0f;\ninline constexpr float WIDE_ASPECT_RATIO = 16.0f / 9.0f;\ninline constexpr float STEAM_DECK_ASPECT_RATIO = 16.0f / 10.0f;\n\ninline float g_aspectRatio;\ninline float g_aspectRatioMovie;\ninline float g_aspectRatioOffsetX;\ninline float g_aspectRatioOffsetY;\ninline float g_aspectRatioMultiplayerOffsetX;\ninline float g_aspectRatioScale;\ninline float g_aspectRatioGameplayScale;\ninline float g_aspectRatioNarrowScale;\ninline float g_aspectRatioNarrowMargin;\ninline float g_horzCentre;\ninline float g_vertCentre;\ninline float g_radarMapScale;\n\nclass AspectRatioPatches\n{\npublic:\n    static void Init();\n    static void ComputeOffsets();\n};\n\n// -------------- CSD MODIFIERS --------------- //\n\nenum CsdFlags : uint64_t\n{\n    CSD_ALIGN_CENTER = 0,\n\n    CSD_ALIGN_TOP = MAKE_BITFLAG64(0),\n    CSD_ALIGN_LEFT = MAKE_BITFLAG64(1),\n    CSD_ALIGN_BOTTOM = MAKE_BITFLAG64(2),\n    CSD_ALIGN_RIGHT = MAKE_BITFLAG64(3),\n\n    CSD_ALIGN_TOP_LEFT = CSD_ALIGN_TOP | CSD_ALIGN_LEFT,\n    CSD_ALIGN_TOP_RIGHT = CSD_ALIGN_TOP | CSD_ALIGN_RIGHT,\n    CSD_ALIGN_BOTTOM_LEFT = CSD_ALIGN_BOTTOM | CSD_ALIGN_LEFT,\n    CSD_ALIGN_BOTTOM_RIGHT = CSD_ALIGN_BOTTOM | CSD_ALIGN_RIGHT,\n\n    CSD_STRETCH_HORIZONTAL = MAKE_BITFLAG64(4),\n    CSD_STRETCH_VERTICAL = MAKE_BITFLAG64(5),\n\n    CSD_STRETCH = CSD_STRETCH_HORIZONTAL | CSD_STRETCH_VERTICAL,\n\n    CSD_SCALE = MAKE_BITFLAG64(6),\n\n    CSD_EXTEND_LEFT = MAKE_BITFLAG64(7),\n    CSD_EXTEND_RIGHT = MAKE_BITFLAG64(8),\n\n    CSD_STORE_LEFT_CORNER = MAKE_BITFLAG64(9),\n    CSD_STORE_RIGHT_CORNER = MAKE_BITFLAG64(10),\n\n    CSD_SKIP = MAKE_BITFLAG64(11),\n\n    CSD_OFFSET_SCALE_LEFT = MAKE_BITFLAG64(12),\n    CSD_OFFSET_SCALE_RIGHT = MAKE_BITFLAG64(13),\n\n    CSD_REPEAT_LEFT = MAKE_BITFLAG64(14),\n    CSD_REPEAT_RIGHT = MAKE_BITFLAG64(15),\n    CSD_REPEAT_FLIP_HORIZONTAL = MAKE_BITFLAG64(16),\n    CSD_REPEAT_FLIP_VERTICAL = MAKE_BITFLAG64(17),\n    CSD_REPEAT_EXTEND = MAKE_BITFLAG64(18),\n\n    CSD_UV_MODIFIER = MAKE_BITFLAG64(19),\n    CSD_COLOUR_MODIFIER = MAKE_BITFLAG64(20),\n    CSD_REPEAT_UV_MODIFIER = MAKE_BITFLAG64(21),\n    CSD_REPEAT_COLOUR_MODIFIER = MAKE_BITFLAG64(22),\n\n    CSD_BLACK_BAR = MAKE_BITFLAG64(23),\n    CSD_PROHIBIT_BLACK_BAR = MAKE_BITFLAG64(24),\n\n    CSD_UNSTRETCH_HORIZONTAL = MAKE_BITFLAG64(25),\n\n    CSD_CORNER_EXTRACT = MAKE_BITFLAG64(26),\n\n    CSD_RADARMAP = MAKE_BITFLAG64(27),\n\n    CSD_POD_BASE = MAKE_BITFLAG64(28),\n    CSD_POD_CLONE = MAKE_BITFLAG64(29),\n\n    CSD_MULTIPLAYER = MAKE_BITFLAG64(30),\n\n    CSD_CHEVRON = MAKE_BITFLAG64(31),\n\n    CSD_MODIFIER_ULTRAWIDE_ONLY = MAKE_BITFLAG64(32),\n    CSD_MODIFIER_NARROW_ONLY = MAKE_BITFLAG64(33),\n\n    CSD_SCENE_DISABLE_MOTION = MAKE_BITFLAG64(34),\n\n    CSD_MOVIE = MAKE_BITFLAG64(35),\n\n    CSD_CRI_LOGO = MAKE_BITFLAG64(36),\n\n    CSD_MAIN_MENU_PARTS_CAST_0221 = MAKE_BITFLAG64(37),\n    CSD_MAIN_MENU_PARTS_CAST_0222 = MAKE_BITFLAG64(38),\n    CSD_MAIN_MENU_PARTS_CAST_0226 = MAKE_BITFLAG64(39),\n    CSD_MAIN_MENU_PARTS_CAST_0227 = MAKE_BITFLAG64(40),\n    \n    CSD_BUTTON_WINDOW = MAKE_BITFLAG64(41)\n};\n\nstruct CsdUVs\n{\n    float U0{};\n    float V0{};\n    float U1{};\n    float V1{};\n    float U2{};\n    float V2{};\n    float U3{};\n    float V3{};\n};\n\nstruct CsdColours\n{\n    uint32_t C0{};\n    uint32_t C1{};\n    uint32_t C2{};\n    uint32_t C3{};\n};\n\nstruct CsdModifier\n{\n    int64_t Flags{};\n    CsdUVs UVs{};\n    CsdColours Colours{};\n    CsdUVs RepeatUVs{};\n    CsdColours RepeatColours{};\n    float CornerMax{};\n    uint32_t CornerIndex{};\n};\n\nextern const xxHashMap<CsdModifier> g_csdModifiers;\n\nstd::optional<CsdModifier> FindCsdModifier(uint32_t data);\n\n// ------------- MOVIE MODIFIERS -------------- //\n\nenum MovieFlags : uint32_t\n{\n    MOVIE_CROP_NARROW = MAKE_BITFLAG32(0),\n    MOVIE_CROP_WIDE = MAKE_BITFLAG32(1),\n    MOVIE_CROP = MOVIE_CROP_NARROW | MOVIE_CROP_WIDE\n};\n\nstruct MovieModifier\n{\n    uint32_t Flags{};\n};\n\nextern const xxHashMap<MovieModifier> g_movieModifiers;\n\nMovieModifier FindMovieModifier(XXH64_hash_t nameHash);\n\n#undef MAKE_BITFLAG64\n#undef MAKE_BITFLAG32\n"
  },
  {
    "path": "MarathonRecomp/patches/audio_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <kernel/function.h>\n#include <os/media.h>\n#include <os/version.h>\n#include <patches/audio_patches.h>\n#include <user/config.h>\n#include <app.h>\n\nint AudioPatches::m_isAttenuationSupported = -1;\n\nbool AudioPatches::CanAttenuate()\n{\n#if _WIN32\n    if (m_isAttenuationSupported >= 0)\n        return m_isAttenuationSupported;\n\n    auto version = os::version::GetOSVersion();\n\n    m_isAttenuationSupported = version.Major >= 10 && version.Build >= 17763;\n\n    return m_isAttenuationSupported;\n#elif __linux__\n    return true;\n#else\n    return false;\n#endif\n}\n\nvoid AudioPatches::Update(float deltaTime)\n{\n    auto pAudioEngine = Sonicteam::AudioEngineXenon::GetInstance();\n\n    if (!pAudioEngine)\n        return;\n\n    const float musicVolume = Config::MusicVolume * Config::MasterVolume;\n\n    if (Config::MusicAttenuation && CanAttenuate())\n    {\n        auto time = 1.0f - expf(2.5f * -deltaTime);\n\n        if (os::media::IsExternalMediaPlaying())\n        {\n            pAudioEngine->m_MusicVolume = std::lerp(pAudioEngine->m_MusicVolume, 0.0f, time);\n        }\n        else\n        {\n            pAudioEngine->m_MusicVolume = std::lerp(pAudioEngine->m_MusicVolume, musicVolume, time);\n        }\n    }\n    else\n    {\n        pAudioEngine->m_MusicVolume = musicVolume;\n    }\n\n    pAudioEngine->m_EffectsVolume = Config::EffectsVolume * Config::MasterVolume;\n}\n\n// Update function for CRI cues.\n// This hook fixes jingles fading the BGM back in prematurely.\nPPC_FUNC_IMPL(__imp__sub_8260F168);\nPPC_FUNC(sub_8260F168)\n{\n    struct CueParams\n    {\n        be<float> Duration;\n        be<float> FrameTime;\n        be<float> Field08;\n        be<float> Field0C;\n        be<float> MusicVolume;\n        bool Field14;\n    };\n\n    auto pParams = (CueParams*)(base + ctx.r3.u32);\n\n    pParams->FrameTime = App::s_deltaTime;\n\n    __imp__sub_8260F168(ctx, base);\n}\n\nvoid CriCueUpdateDeltaTimeFix(PPCRegister& deltaTime)\n{\n    deltaTime.f64 = App::s_deltaTime;\n}\n\nvoid PowerUpJingleDurationFix(PPCRegister& duration)\n{\n    if (!Config::FixPowerUpJingleDuration)\n        return;\n\n    duration.f64 = 20.0;\n}\n\nvoid XmvPlayerLang(PPCRegister& r11)\n{\n    r11.u32 = 1;\n\n    if (Config::VoiceLanguage == EVoiceLanguage::Japanese)\n        r11.u32++;\n}\n\nvoid CsbSbkLang(PPCRegister& r8)\n{\n    r8.u32 = Config::VoiceLanguage == EVoiceLanguage::Japanese ? 0 : 1;\n}\n\nvoid MovieVoiceLang(PPCRegister& r19)\n{\n    r19.u32 = Config::VoiceLanguage == EVoiceLanguage::Japanese ? 0x80000000 : 0x40000000;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/audio_patches.h",
    "content": "#pragma once\n\nclass AudioPatches\n{\nprotected:\n    static int m_isAttenuationSupported;\n\npublic:\n    static bool CanAttenuate();\n    static void Update(float deltaTime);\n};\n"
  },
  {
    "path": "MarathonRecomp/patches/camera_patches.cpp",
    "content": "#include \"camera_patches.h\"\n#include <api/Marathon.h>\n#include <patches/aspect_ratio_patches.h>\n#include <patches/MainMenuTask_patches.h>\n#include <user/config.h>\n\nstatic class MainMenuCameraUpdateEvent : public ContextHookEvent<Sonicteam::MainMenuTask>\n{\npublic:\n    void Update(Sonicteam::MainMenuTask* pThis, float deltaTime) override\n    {\n        auto pMainMode = pThis->GetDocMode<Sonicteam::MainMode>();\n\n        if (!pMainMode)\n            return;\n\n        pMainMode->m_spSelectCamera->Update();\n    }\n}\ng_mainMenuCameraUpdateEvent{};\n\nvoid CameraPatches::Init()\n{\n    MainMenuTaskPatches::s_events.push_back(&g_mainMenuCameraUpdateEvent);\n}\n\nvoid CameraImp_SetFOV(PPCRegister& f1)\n{\n    if (g_aspectRatio >= WIDE_ASPECT_RATIO)\n        return;\n\n    f1.f64 = 2.0 * atan(tan(0.5 * f1.f64) / g_aspectRatio * WIDE_ASPECT_RATIO);\n}\n\nvoid SonicCamera_InvertAzDriveK(PPCRegister& az_driveK)\n{\n    // X axis is inverted by default.\n    if (Config::HorizontalCamera == ECameraRotationMode::Reverse)\n        return;\n\n    az_driveK.f64 = -az_driveK.f64;\n}\n\nvoid SonicCamera_InvertAltDriveK(PPCRegister& alt_driveK)\n{\n    // Y axis is not inverted by default.\n    if (Config::VerticalCamera == ECameraRotationMode::Normal)\n        return;\n\n    alt_driveK.f64 = -alt_driveK.f64;\n}\n\nvoid DemoGMCamera_InvertHorizontal(PPCRegister& horz)\n{\n    // X axis is inverted by default.\n    if (Config::HorizontalCamera == ECameraRotationMode::Reverse)\n        return;\n\n    horz.f64 = -horz.f64;\n}\n\nvoid DemoGMCamera_InvertVertical(PPCRegister& vert)\n{\n    // Y axis is inverted by default.\n    if (Config::VerticalCamera == ECameraRotationMode::Reverse)\n        return;\n\n    vert.f64 = -vert.f64;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/camera_patches.h",
    "content": "#pragma once\n\nclass CameraPatches\n{\npublic:\n    static void Init();\n};\n"
  },
  {
    "path": "MarathonRecomp/patches/fps_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <user/config.h>\n#include <app.h>\n\nconstexpr double REFERENCE_DELTA_TIME = 1.0 / 60.0;\n\n// Only use this in threaded context with fixed delta time!\ndouble MakeDeltaTime(std::chrono::steady_clock::time_point& prev)\n{\n    auto now = std::chrono::steady_clock::now();\n    auto deltaTime = std::min(std::chrono::duration<double>(now - prev).count(), 1.0 / 15.0);\n\n    prev = now;\n    now = std::chrono::steady_clock::now();\n\n    return deltaTime;\n}\n\nbool HasFrameElapsed(double reference, double& timeElapsed, double deltaTime)\n{\n    timeElapsed += deltaTime;\n\n    if (timeElapsed < reference)\n        return false;\n\n    timeElapsed = 0.0f;\n\n    return true;\n}\n\n// Sonicteam::SoX::Physics::Havok::WorldHavok::Update\nPPC_FUNC_IMPL(__imp__sub_82587AA8);\nPPC_FUNC(sub_82587AA8)\n{\n    auto pPhysicsWorld = (Sonicteam::SoX::Physics::Havok::WorldHavok*)(base + ctx.r3.u32);\n\n    // Use dynamic update rate if the FPS exceeds the fixed rate.\n    pPhysicsWorld->m_IsDynamicUpdateRate = Config::FPS > 120;\n\n    __imp__sub_82587AA8(ctx, base);\n}\n\nvoid PostureControl_RotationSpeedFix(PPCRegister& c_rotation_speed, PPCRegister& stack)\n{\n    auto deltaTime = *(be<double>*)g_memory.Translate(stack.u32 + 0x200);\n\n    c_rotation_speed.f64 = (c_rotation_speed.f64 * (60.0 * deltaTime));\n}\n\nvoid SonicCamera_RotationSpeedFix(PPCRegister& f0, PPCRegister& deltaTime, PPCRegister& f13)\n{\n    f0.f64 = float((f0.f64 * (deltaTime.f64 * (1.0 / (deltaTime.f64 * 60.0)))) + f13.f64);\n}\n\nbool ObjTarzan_VolatileBranch(PPCRegister& r30)\n{\n    if (Config::FPS <= 60)\n        return false;\n\n    return r30.f64 >= 0;\n}\n\nvoid ObjTarzan_PatchStaticDeltaTime(PPCRegister& value, PPCRegister& delta)\n{\n    if (Config::FPS <= 60)\n        return;\n\n    value.f64 = delta.f64;\n}\n\nvoid ObjTarzan_PatchDeltaTimeArgument(PPCRegister& value, PPCRegister& value2, PPCRegister& stack)\n{\n    if (Config::FPS <= 60)\n        return;\n\n    auto deltaTime = *(be<double>*)g_memory.Translate(stack.u32 + 0x90 + 0x420 - 0x88);\n\n    value.f64 = deltaTime;\n    value2.f64 = deltaTime;\n}\n\n// Sonicteam::ObjTarzan::UpdatePoint (speculatory)\nPPC_FUNC_IMPL(__imp__sub_8232D770);\nPPC_FUNC(sub_8232D770)\n{\n    if (Config::FPS <= 60)\n    {\n        __imp__sub_8232D770(ctx, base);\n        return;\n    }\n\n    struct TarzanPoint\n    {\n        MARATHON_INSERT_PADDING(0x4C);\n        be<float> m_Time;\n        MARATHON_INSERT_PADDING(0xB0);\n    };\n\n    auto pTarzanPoint = (TarzanPoint*)(base + ctx.r3.u32);\n    auto deltaTime = ctx.f1.f64;\n\n    if (deltaTime > 1.0)\n        deltaTime = 1.0;\n\n    do\n    {\n        GuestToHostFunction<void>(sub_8232D288, pTarzanPoint, ctx.f1.u64, deltaTime, deltaTime, deltaTime);\n        pTarzanPoint->m_Time = pTarzanPoint->m_Time - deltaTime;\n    }\n    while (pTarzanPoint->m_Time >= deltaTime);\n}\n\nvoid ObjEspSwing_DecayRateFix(PPCRegister& f0, PPCRegister& f13, PPCRegister& deltaTime)\n{\n    f0.f64 = float(f13.f64 * pow(pow(f0.f64, 60.0), deltaTime.f64));\n}\n\nstruct MsgSuckPlayerEx : public Sonicteam::Message::Player::MsgSuckPlayer\n{\n    be<float> DeltaTime;\n};\n\nvoid ObjectInputWarp_ExtendMsgSuckPlayer(PPCRegister& phantom, PPCRegister& message, PPCRegister& deltaTime)\n{\n    auto pPhantom = (Sonicteam::SoX::Physics::Phantom*)g_memory.Translate(phantom.u32);\n    auto pMessage = (Sonicteam::Message::Player::MsgSuckPlayer*)g_memory.Translate(message.u32);\n\n    auto pMsgSuckPlayerEx = (MsgSuckPlayerEx*)g_userHeap.Alloc(sizeof(MsgSuckPlayerEx));\n    pMsgSuckPlayerEx->ID = pMessage->ID;\n    pMsgSuckPlayerEx->Point = pMessage->Point;\n    pMsgSuckPlayerEx->DeltaTime = deltaTime.f64;\n\n    pPhantom->ProcessMessage(pMsgSuckPlayerEx);\n\n    g_userHeap.Free(pMsgSuckPlayerEx);\n}\n\nvoid PlayerObject_ProcessMsgSuckPlayer_FixForce(PPCRegister& message, PPCRegister& force)\n{\n    auto pMessage = (MsgSuckPlayerEx*)g_memory.Translate(message.u32);\n\n    force.f64 = pow(force.f64, pMessage->DeltaTime * 60.0);\n}\n\nvoid PlayerObject_ProcessMsgSuckPlayer_FixDeltaTime(PPCRegister& message, PPCRegister& deltaTime)\n{\n    auto pMessage = (MsgSuckPlayerEx*)g_memory.Translate(message.u32);\n\n    deltaTime.f64 = pMessage->DeltaTime;\n}\n\nvoid Spanverse_GE1PE_DeltaTimeFix(PPCRegister& deltaTime)\n{\n    deltaTime.f64 = App::s_deltaTime;\n}\n\n// Allocate more space to store the previous loading\n// time for this instance of Sonicteam::HUDLoading.\nvoid HUDLoadingAlloc(PPCRegister& r3)\n{\n    r3.u32 += sizeof(std::chrono::steady_clock::time_point);\n}\n\n// Sonicteam::HUDLoading::HUDLoading\nPPC_FUNC_IMPL(__imp__sub_824D7BC8);\nPPC_FUNC(sub_824D7BC8)\n{\n    auto pPrevLoadingTime = (std::chrono::steady_clock::time_point*)(base + ctx.r3.u32 + sizeof(Sonicteam::HUDLoading));\n\n    *pPrevLoadingTime = {};\n\n    __imp__sub_824D7BC8(ctx, base);\n}\n\n// Accumulate own delta time and provide it to the CSD update function.\nvoid HUDLoading_DeltaTimeFix(PPCRegister& pThis, PPCRegister& deltaTime)\n{\n    auto pPrevLoadingTime = (std::chrono::steady_clock::time_point*)g_memory.Translate(pThis.u32 + sizeof(Sonicteam::HUDLoading));\n\n    deltaTime.f64 = MakeDeltaTime(*pPrevLoadingTime);\n}\n\n// This function is an override of Sonicteam::SoX::Engine::Task::Update,\n// it does not pass delta time to HUDCALLBACK::Update, so we must preserve\n// the register here in order to do it ourselves.\nvoid HUDWindow_PreserveDeltaTime(PPCRegister& f31, PPCRegister& f1)\n{\n    f31.f64 = f1.f64;\n}\n\n// HUDCALLBACK::Update doesn't have a delta time argument,\n// so we pass one in here to do some delta time accumulation\n// safely in the hooks below.\nvoid HUDWindow_Callback(PPCRegister& f1, PPCRegister& f31)\n{\n    f1.f64 = f31.f64;\n}\n\n// Sonicteam::PriceListWindowTask::HUDCALLBACK::Update\nPPC_FUNC_IMPL(__imp__sub_8250AAB0);\nPPC_FUNC(sub_8250AAB0)\n{\n    static auto s_time = 0.0;\n\n    // Fix for white ⇄ red text breathing animation.\n    if (!HasFrameElapsed(REFERENCE_DELTA_TIME, s_time, ctx.f1.f64))\n        return;\n\n    __imp__sub_8250AAB0(ctx, base);\n}\n\n// Sonicteam::SelectWindowTask::HUDCALLBACK::Update\nPPC_FUNC_IMPL(__imp__sub_8250D698);\nPPC_FUNC(sub_8250D698)\n{\n    static auto s_time = 0.0;\n\n    // Fix for white ⇄ red text breathing animation.\n    if (!HasFrameElapsed(REFERENCE_DELTA_TIME, s_time, ctx.f1.f64))\n        return;\n\n    __imp__sub_8250D698(ctx, base);\n}\n\nbool ObjectVehicleBike_EnemyShot_DisableVehicleCollisionLayer(PPCRegister& r3)\n{\n    if (Config::FPS <= 60)\n        return false;\n\n    auto pObjectVehicleBike = (Sonicteam::ObjectVehicleBike*)g_memory.Translate(r3.u32);\n    auto pPrefixDependency = pObjectVehicleBike->m_pDependency->GetFirstDependency();\n\n    // Call original function.\n    sub_822C1FA8(*GetPPCContext(), g_memory.base);\n\n    auto pPostfixDependency = pObjectVehicleBike->m_pDependency->GetFirstDependency();\n    auto isUpdatePhysicsWorld = false;\n\n    // Process rigid bodies created after the function call.\n    while (pPrefixDependency != pPostfixDependency)\n    {\n        pPrefixDependency = pPrefixDependency->m_pPrevSibling.get();\n\n        if (strcmp(pPrefixDependency->GetName(), \"EnemyShotNormal\") == 0)\n        {\n            auto pEnemyShotNormal = (Sonicteam::Enemy::EnemyShotNormal*)pPrefixDependency;\n\n            if (auto pPhantom = pEnemyShotNormal->m_spPhantom.get())\n            {\n                // Fix bullets colliding with the bike at high frame rates.\n                auto& collidable = pPhantom->m_pRigidBody->m_collidable;\n                collidable.m_broadPhaseHandle.m_collisionFilterInfo = 0xFFFF5E00;\n                isUpdatePhysicsWorld = true;\n            }\n        }\n    }\n\n    if (isUpdatePhysicsWorld)\n    {\n        if (auto pGameMode = App::s_pApp->m_pDoc->GetDocMode<Sonicteam::GameMode>())\n        {\n            auto pWorldHavok = pGameMode->GetGame()->GetPhysicsWorld<Sonicteam::SoX::Physics::Havok::WorldHavok>();\n\n            if (auto pWorld = pWorldHavok->m_pWorld)\n                pWorld->updateCollisionFilterOnWorld(1, 1);\n        }\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/frontend_listener.cpp",
    "content": "#include <kernel/memory.h>\n#include <ui/options_menu.h>\n#include <os/logger.h>\n#include <user/config.h>\n#include <sdl_listener.h>\n\nstatic class FrontendListener : public SDLEventListener\n{\n    bool m_isF8KeyDown = false;\n\npublic:\n    bool OnSDLEvent(SDL_Event* event) override\n    {\n        if (!Config::HUDToggleKey || OptionsMenu::s_isVisible)\n            return false;\n\n        switch (event->type)\n        {\n        case SDL_KEYDOWN:\n        {\n            if (event->key.keysym.sym != SDLK_F8 || m_isF8KeyDown)\n                break;\n\n            // TODO\n\n            m_isF8KeyDown = true;\n\n            break;\n        }\n\n        case SDL_KEYUP:\n            m_isF8KeyDown = event->key.keysym.sym != SDLK_F8;\n            break;\n        }\n\n        return false;\n    }\n}\ng_frontendListener;\n"
  },
  {
    "path": "MarathonRecomp/patches/hook_event.h",
    "content": "#pragma once\n\nclass IHookEvent\n{\npublic:\n    virtual ~IHookEvent() = default;\n    virtual void Prefix() = 0;\n    virtual void Update(float deltaTime) = 0;\n    virtual void Postfix() = 0;\n};\n\nclass HookEvent : public IHookEvent\n{\npublic:\n    void Prefix() override {}\n    void Update(float deltaTime) override {}\n    void Postfix() override {}\n};\n\ntemplate <typename T>\nclass IContextHookEvent\n{\npublic:\n    virtual ~IContextHookEvent() = default;\n    virtual void Prefix(T* pThis) = 0;\n    virtual void Update(T* pThis, float deltaTime) = 0;\n    virtual void Postfix(T* pThis) = 0;\n};\n\ntemplate <typename T>\nclass ContextHookEvent : public IContextHookEvent<T>\n{\npublic:\n    void Prefix(T* pThis) override {}\n    void Update(T* pThis, float deltaTime) override {}\n    void Postfix(T* pThis) override {}\n};\n"
  },
  {
    "path": "MarathonRecomp/patches/input_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <user/config.h>\n\nstatic constexpr int INPUT_LISTENER_B_DOWN = 0x2000000;\n\n// Sonicteam::Player::Input::ListenerNormal::Update\nPPC_FUNC_IMPL(__imp__sub_82222428);\nPPC_FUNC(sub_82222428)\n{\n    auto pListenerNormal = (Sonicteam::Player::Input::ListenerNormal*)(base + ctx.r3.u32);\n    auto pInputManager = (Sonicteam::SoX::Input::Manager*)(base + ctx.r4.u32);\n\n    __imp__sub_82222428(ctx, base);\n\n    if (*pListenerNormal->m_pIsListening)\n    {\n        auto& rPadState = pInputManager->m_PadState;\n\n        // Add B down support to the normal input listener.\n        if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_B))\n            pListenerNormal->m_State = pListenerNormal->m_State.get() | INPUT_LISTENER_B_DOWN;\n    }\n}\n\nvoid RemapAntigravityEnter(PPCRegister& r11, PPCRegister& r28)\n{\n    auto state = 0x800;\n\n    if (Config::SlidingAttack == ESlidingAttack::B)\n        state = INPUT_LISTENER_B_DOWN;\n\n    r11.u64 = (r28.u32 & state) != 0;\n}\n\nvoid RemapAntigravityExit(PPCRegister& r11, PPCRegister& r30)\n{\n    if (Config::SlidingAttack == ESlidingAttack::X)\n        return;\n\n    r11.u64 = (r30.u32 & INPUT_LISTENER_B_DOWN) == 0;\n}\n\nvoid RemapLightDash(PPCRegister& r3, PPCRegister& r11)\n{\n    r11.u64 = ((r3.u32 >> (Config::LightDash == ELightDash::X ? 8 : 15)) & 1) != 0;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/loading_patches.cpp",
    "content": "#include \"loading_patches.h\"\n#include <api/Marathon.h>\n\n// Sonicteam::HUDLoading::Update\nPPC_FUNC_IMPL(__imp__sub_824D7340);\nPPC_FUNC(sub_824D7340)\n{\n    auto pHUDLoading = (Sonicteam::HUDLoading*)(base + ctx.r3.u32);\n\n    if ((pHUDLoading->m_Flags.get() & Sonicteam::HUDLoading::HUDLoadingFlags_Finished) == 0)\n    {\n        for (auto& event : LoadingPatches::Events)\n            event->Update(ctx.f1.f64);\n    }\n\n    __imp__sub_824D7340(ctx, base);\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/loading_patches.h",
    "content": "#pragma once\n\n#include \"hook_event.h\"\n\nclass LoadingPatches\n{\npublic:\n    static inline std::vector<IHookEvent*> Events{};\n};\n"
  },
  {
    "path": "MarathonRecomp/patches/misc_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <hid/hid.h>\n#include <ui/black_bar.h>\n#include <user/config.h>\n#include <user/achievement_manager.h>\n#include <app.h>\n\nvoid UnlockAchievement(PPCRegister& id)\n{\n    AchievementManager::Unlock(id.u32);\n}\n\nvoid SetLifeBarAnimation(PPCRegister& r3, PPCRegister& r4, PPCRegister& r5, PPCRegister& r31)\n{\n    static bool s_initContextualHUD{};\n    static char* s_lifeBarSceneName{};\n\n    if (!s_initContextualHUD)\n    {\n        constexpr const char* LIFE_BAR_ANIME = \"life_bar_anime\";\n\n        s_lifeBarSceneName = (char*)g_userHeap.Alloc(strlen(LIFE_BAR_ANIME) + 1);\n\n        strcpy(s_lifeBarSceneName, LIFE_BAR_ANIME);\n\n        if (Config::RestoreContextualHUDColours)\n        {\n            *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Shadow] = 1.0f;\n            *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Omega] = 1.0f;\n            *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Rouge] = 1.0f;\n            *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Silver] = 2.0f;\n            *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Amy] = 2.0f;\n            *Sonicteam::Globals::ms_MainDisplayColours[Sonicteam::Character_Blaze] = 2.0f;\n        }\n\n        s_initContextualHUD = true;\n    }\n\n    auto base = g_memory.base;\n    auto pCsdObject = (Sonicteam::CsdObject*)(base + PPC_LOAD_U32(r3.u32));\n    auto pSceneName = (const char*)(base + r4.u32);\n\n    // Redirect \"life_ber_anime\" to \"life_bar_anime\", as they\n    // actually spelt \"bar\" correctly in the tag XNCP scene names...\n    if ((pCsdObject->m_pCsdResource->m_FilePath == \"sprite/tagdisplay_1p\" ||\n        pCsdObject->m_pCsdResource->m_FilePath == \"sprite/tagdisplay_2p\") &&\n        strcmp(pSceneName, \"life_ber_anime\") == 0)\n    {\n        r4.u32 = g_memory.MapVirtual(s_lifeBarSceneName);\n    }\n\n    if (!Config::RestoreContextualHUDColours)\n        return;\n\n    auto pHUDMainDisplay = (Sonicteam::HUDMainDisplay*)g_memory.Translate(r31.u32);\n\n    switch (pHUDMainDisplay->m_Character)\n    {\n        case Sonicteam::Character_Sonic:\n        case Sonicteam::Character_Tails:\n        case Sonicteam::Character_Knuckles:\n            r5.u32 = 0x82036778; // \"sonic_in\"\n            break;\n\n        case Sonicteam::Character_Shadow:\n        case Sonicteam::Character_Omega:\n        case Sonicteam::Character_Rouge:\n            r5.u32 = 0x8203676C; // \"shadow_in\"\n            break;\n\n        case Sonicteam::Character_Silver:\n        case Sonicteam::Character_Amy:\n        case Sonicteam::Character_Blaze:\n            r5.u32 = 0x82036760; // \"silver_in\"\n            break;\n    }\n}\n\nvoid SetRingBarIndex(PPCRegister& r5, PPCRegister& r31)\n{\n    if (!Config::RestoreContextualHUDColours)\n        return;\n\n    auto pHUDMainDisplay = (Sonicteam::HUDMainDisplay*)g_memory.Translate(r31.u32);\n\n    r5.u32 = pHUDMainDisplay->m_Character;\n}\n\n// Redirects 2P HUD to 1P HUD to remove overscan compensation.\nvoid Load2PDisplayMidAsmHook() {}\n\nvoid PostureDisableEdgeGrabLeftover(PPCRegister& posture)\n{\n    if (!Config::DisableEdgeGrabLeftover)\n        return;\n\n    auto base = g_memory.base;\n\n    *(volatile uint8_t*)(base + (posture.u32 + 0x3C0)) = 1;\n}\n\nvoid PedestrianAnimationLOD(PPCRegister& val)\n{\n    val.u32 = 0;\n}\n\n// Sonicteam::CommonObjectHint::Update\nPPC_FUNC_IMPL(__imp__sub_822CE930);\nPPC_FUNC(sub_822CE930)\n{\n    auto* pCommonObjectHint = (Sonicteam::CommonObjectHint*)(base + ctx.r3.u32);\n    auto* pGame = App::s_pApp->m_pDoc->GetDocMode<Sonicteam::GameMode>()->GetGame();\n    auto& rMessageName = pCommonObjectHint->m_MessageName;\n\n    if (!Config::Hints && pCommonObjectHint->m_Type == Sonicteam::CommonObjectHint::CommonObjectHintType_HintRing)\n    {\n        pCommonObjectHint->Destroy();\n        return;\n    }\n\n    if (!Config::ControlTutorial || Config::SlidingAttack != ESlidingAttack::X)\n    {\n        // Get global flag for Sonic's Antigravity being unlocked to remove \"hint_all03_h26_so\".\n        guest_stack_var<Sonicteam::Message::Mission::MsgGetGlobalFlag> msgGetSonicAntigravityFlag(6001);\n        pGame->m_pMissionCore->ProcessMessage(msgGetSonicAntigravityFlag.get());\n\n        if (msgGetSonicAntigravityFlag->FlagValue != 0 && strcmp(rMessageName, \"hint_twn01_e02_tl\") == 0)\n        {\n            pCommonObjectHint->Destroy();\n            return;\n        }\n    }\n\n    if (!Config::ControlTutorial || Config::LightDash != ELightDash::X)\n    {\n        // Get global flag for Sonic's Light Dash being unlocked to remove \"hint_twn01_e01_tl\".\n        guest_stack_var<Sonicteam::Message::Mission::MsgGetGlobalFlag> msgGetSonicLightDashFlag(6000);\n        pGame->m_pMissionCore->ProcessMessage(msgGetSonicLightDashFlag.get());\n\n        // Get global flag for Shadow's Light Dash being unlocked to remove \"hint_twn01_e44_rg\".\n        guest_stack_var<Sonicteam::Message::Mission::MsgGetGlobalFlag> msgGetShadowLightDashFlag(6012);\n        pGame->m_pMissionCore->ProcessMessage(msgGetShadowLightDashFlag.get());\n\n        auto isSonicLightDashHint = msgGetSonicLightDashFlag->FlagValue != 0 && strcmp(rMessageName, \"hint_twn01_e00_tl\") == 0;\n        auto isShadowLightDashHint = msgGetShadowLightDashFlag->FlagValue != 0 && strcmp(rMessageName, \"hint_twn01_e43_rg\") == 0;\n\n        if (isSonicLightDashHint || isShadowLightDashHint ||\n            strcmp(rMessageName, \"hint_all03_h00_so\") == 0 ||\n            strcmp(rMessageName, \"hint_all03_h31_so\") == 0)\n        {\n            pCommonObjectHint->Destroy();\n            return;\n        }\n    }\n\n    auto isPlayStation = Config::ControllerIcons == EControllerIcons::PlayStation;\n\n    if (Config::ControllerIcons == EControllerIcons::Auto)\n        isPlayStation = hid::g_inputDeviceController == hid::EInputDevice::PlayStation;\n\n    if (!Config::ControlTutorial || isPlayStation)\n    {\n        guest_stack_var<int> stack{};\n\n        auto pspTextCard = GuestToHostFunction<boost::shared_ptr<Sonicteam::TextCard>*>(sub_825ECB48,\n            stack.get(), App::s_pApp->GetGame()->m_pHintTextBook.get(), (const char*)rMessageName);\n\n        if (auto pTextCard = pspTextCard->get())\n        {\n            if (pTextCard->m_pVariables)\n            {\n                if (!Config::ControlTutorial)\n                {\n                    if (strstr(pTextCard->m_pVariables, \"picture(button_\"))\n                    {\n                        pCommonObjectHint->Destroy();\n                        return;\n                    }\n                }\n\n                if (isPlayStation)\n                {\n                    // L1/R1 and L2/R2 are flipped on PS3, leading\n                    // to the voice lines being wrong for the hints.\n                    // \n                    // We'll provide an alternate control scheme to\n                    // address this later on, but for now these should\n                    // be hidden.\n\n                    if (strstr(pTextCard->m_pVariables, \"button_lb\") || strstr(pTextCard->m_pVariables, \"button_rb\"))\n                    {\n                        pCommonObjectHint->Destroy();\n                        return;\n                    }\n                }\n            }\n        }\n    }\n\n    __imp__sub_822CE930(ctx, base);\n}\n\nPPC_FUNC_IMPL(__imp__sub_8244D288);\nPPC_FUNC(sub_8244D288)\n{\n    auto isShadowEggCerberus = PPC_LOAD_U32(ctx.r3.u32 + 0x1C);\n\n    // Prevent Eggman's voice line playing\n    // for Shadow's variant of Egg Cerberus.\n    if (!Config::Hints && isShadowEggCerberus)\n        return;\n\n    __imp__sub_8244D288(ctx, base);\n}\n\nbool Super3_DisableChangeRequestHint()\n{\n    return !Config::ControlTutorial;\n}\n\nPPC_FUNC_IMPL(__imp__sub_824A6EA8);\nPPC_FUNC(sub_824A6EA8)\n{\n    if ((App::s_isSkipLogos || Config::SkipIntroLogos) && ctx.r4.u32 == 1)\n        ctx.r4.u32 = 4;\n\n    __imp__sub_824A6EA8(ctx, base);\n}\n\nPPC_FUNC(sub_82188460)\n{\n    ctx.r3.u64 = Config::Subtitles;\n}\n\nvoid NOP() {}\n"
  },
  {
    "path": "MarathonRecomp/patches/patches.h",
    "content": "#include \"aspect_ratio_patches.h\"\n#include \"camera_patches.h\"\n\ninline void InitPatches()\n{\n    AspectRatioPatches::Init();\n    CameraPatches::Init();\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/pause_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <kernel/memory.h>\n#include <ui/options_menu.h>\n\nvoid AddPauseMenuItem\n(\n    Sonicteam::TextBook* in_pTextBook,\n    stdx::vector<stdx::string>* in_pvActionNames,\n    stdx::vector<Sonicteam::TextCard>* in_pvTextCards,\n    stdx::vector<int>* in_pvEnabledItems,\n    const char* in_pActionName,\n    const char* in_pTextName,\n    bool in_isEnabled\n)\n{\n    guest_stack_var<stdx::string> actionName(in_pActionName);\n    guest_stack_var<stdx::string> textName(in_pTextName);\n    guest_stack_var<boost::shared_ptr<Sonicteam::TextCard>> spTextCard;\n    guest_stack_var<int> isEnabled((int)in_isEnabled);\n\n    GuestToHostFunction<int>(sub_8217D608, in_pvActionNames, actionName.get());\n    GuestToHostFunction<int>(sub_825ECB48, spTextCard.get(), in_pTextBook, textName->c_str());\n    GuestToHostFunction<int>(sub_8239E8F0, in_pvTextCards, spTextCard.get());\n    GuestToHostFunction<int>(sub_823879C8, in_pvEnabledItems, isEnabled.get());\n}\n\nvoid GameImp_PauseMenu_AddQuitPrefix(PPCRegister& r1, PPCRegister& r30)\n{\n    auto pGameImp = (Sonicteam::GameImp*)g_memory.Translate(r30.u32);\n    auto pvActionNames = (stdx::vector<stdx::string>*)g_memory.Translate(r1.u32 + 0x70);\n    auto pvTextCards = (stdx::vector<Sonicteam::TextCard>*)g_memory.Translate(r1.u32 + 0x60);\n    auto pvEnabledItems = (stdx::vector<int>*)g_memory.Translate(r1.u32 + 0x80);\n\n    AddPauseMenuItem(pGameImp->m_pSystemTextBook, pvActionNames, pvTextCards, pvEnabledItems, \"options\", \"msg_options\", true);\n}\n\n// Sonicteam::PauseAdapter::MapActionNameToID (speculatory)\nPPC_FUNC_IMPL(__imp__sub_8216DA08);\nPPC_FUNC(sub_8216DA08)\n{\n    auto pPauseAdapter = (Sonicteam::PauseAdapter*)(base + ctx.r3.u32);\n    auto pMsgGetText = (Sonicteam::Message::PauseAdapter::MsgGetText*)(base + ctx.r4.u32);\n\n    __imp__sub_8216DA08(ctx, base);\n\n    // Set selected ID to unused slot.\n    if (pMsgGetText->SelectedName == \"options\")\n        pPauseAdapter->m_SelectedID = 6;\n}\n\n// Sonicteam::PauseAdapter::DoAction (speculatory)\nPPC_FUNC_IMPL(__imp__sub_82170E48);\nPPC_FUNC(sub_82170E48)\n{\n    auto pPauseAdapter = (Sonicteam::PauseAdapter*)(base + ctx.r3.u32);\n\n    if (pPauseAdapter->m_SelectedID == 6)\n    {\n        OptionsMenu::s_pBgmCue = pPauseAdapter->GetGame()->GetBgmCue();\n        OptionsMenu::Open(true);\n        return;\n    }\n\n    __imp__sub_82170E48(ctx, base);\n}\n\n// Sonicteam::PauseTask::Update\nPPC_FUNC_IMPL(__imp__sub_82509870);\nPPC_FUNC(sub_82509870)\n{\n    auto pPauseTask = (Sonicteam::PauseTask*)(base + ctx.r3.u32);\n\n    static bool s_isReturningFromOptionsMenu{};\n\n    switch (pPauseTask->m_State)\n    {\n        case Sonicteam::PauseTask::PauseTaskState_Opening:\n        case Sonicteam::PauseTask::PauseTaskState_Idle:\n        {\n            if (!s_isReturningFromOptionsMenu)\n                break;\n\n            // Set cursor to Options (should always be above the last item).\n            pPauseTask->m_SelectedIndex = pPauseTask->m_ItemCount - 2;\n\n            s_isReturningFromOptionsMenu = false;\n\n            break;\n        }\n\n        case Sonicteam::PauseTask::PauseTaskState_Closed:\n        {\n            if (OptionsMenu::s_isVisible)\n            {\n                if (OptionsMenu::s_state == OptionsMenuState::Closing)\n                {\n                    pPauseTask->m_State = Sonicteam::PauseTask::PauseTaskState_Opened;\n                    s_isReturningFromOptionsMenu = true;\n                }\n                else\n                {\n                    return;\n                }\n            }\n\n            break;\n        }\n    }\n\n    __imp__sub_82509870(ctx, base);\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/player_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <cpu/guest_stack_var.h>\n#include <os/logger.h>\n#include <user/config.h>\n#include <app.h>\n\n// Sonicteam::Player::Object::Update\nPPC_FUNC_IMPL(__imp__sub_82195500);\nPPC_FUNC(sub_82195500)\n{\n    auto pPlayer = (Sonicteam::Player::Object*)(base + ctx.r3.u32);\n    auto pInputManager = pPlayer->GetInputManager();\n\n    if (pPlayer->m_IsPlayer && pInputManager)\n    {\n        if (Config::EnableDebugMode)\n        {\n            switch (pPlayer->m_SetupModuleIndexPostfix)\n            {\n                case 1:\n                {\n                    // Toggle debug posture on Select press.\n                    if (pPlayer->m_SetupModuleIndexPrefix == 1 && pInputManager->m_PadState.IsPressed(Sonicteam::SoX::Input::KeyState_Select))\n                    {\n                        pPlayer->m_SetupModuleIndexPostfix = 2;\n\n                        LOGN(\"Debug Mode: Enabled\");\n                    }\n\n                    break;\n                }\n\n                case 2:\n                {\n                    // Toggle camera volume collision on B press.\n                    if (pInputManager->m_PadState.IsPressed(Sonicteam::SoX::Input::KeyState_B))\n                    {\n                        auto pGame = App::s_pApp->GetGame();\n                        auto pZock = pPlayer->GetPlugin<Sonicteam::Player::Zock>(\"zock\");\n                        auto collisionFilterInfo = pZock->m_spPhantomA->m_pRigidBody->m_collidable.m_broadPhaseHandle.m_collisionFilterInfo == 6 ? 0x383 : 6;\n\n                        LOGFN(\"Camera Volumes: {}\", collisionFilterInfo != 6 ? \"Enabled\" : \"Disabled\");\n\n                        pZock->m_spPhantomA->m_pRigidBody->m_collidable.m_broadPhaseHandle.m_collisionFilterInfo = collisionFilterInfo;\n                        pGame->GetPhysicsWorld<Sonicteam::SoX::Physics::Havok::WorldHavok>()->m_pWorld->updateCollisionFilterOnWorld(1, 1);\n                    }\n\n                    break;\n                }\n            }\n        }\n\n        // Toggle demo camera on right stick press.\n        if (Config::RestoreDemoCameraMode && pInputManager->m_PadState.IsPressed(Sonicteam::SoX::Input::KeyState_RightStick))\n        {\n            auto pCameraman = static_cast<Sonicteam::Camera::Cameraman*>(pPlayer->m_pCameraman.get());\n\n            if (auto pCameraMode = pCameraman->m_spCameraModeManager->m_spCameraMode.get())\n            {\n                guest_stack_var<Sonicteam::Message::Camera::Cameraman::MsgChangeMode> msgChangeMode;\n                msgChangeMode->PadID = pInputManager->m_PadID;\n                msgChangeMode->TargetActorID = pPlayer->m_ActorID;\n                msgChangeMode->IsDemoCamera = pCameraMode->m_pVftable.ptr != 0x82002004;\n\n                LOGFN(\"Demo Camera: {}\", msgChangeMode->IsDemoCamera ? \"Enabled\" : \"Disabled\");\n\n                pCameraman->ProcessMessage(msgChangeMode.get());\n            }\n        }\n    }\n\n    __imp__sub_82195500(ctx, base);\n}\n\n// Sonicteam::Player::State::TailsContext::Update\nPPC_FUNC_IMPL(__imp__sub_8221A7D8);\nPPC_FUNC(sub_8221A7D8)\n{\n    if (!Config::TailsGauge)\n    {\n        __imp__sub_8221A7D8(ctx, base);\n        return;\n    }\n\n    auto pTailsContext = (Sonicteam::Player::State::TailsContext*)(base + ctx.r3.u32);\n    auto pPlayer = pTailsContext->m_spScore->m_pPlayer;\n\n    if (auto pGauge = pPlayer->GetGauge<Sonicteam::Player::SonicGauge>())\n    {\n        pGauge->c_gauge_max = 100.0f;\n        pGauge->m_Value = (100.0f / pTailsContext->m_FlightDuration) * pTailsContext->m_FlightTime;\n    }\n\n    auto pTailsFlight = pPlayer->m_spStateMachine->GetBase()->GetState<Sonicteam::Player::State::TailsFlight>();\n    auto pGame = App::s_pApp->GetGame();\n\n    auto maturityValue = 1.0f;\n\n    // Set maturity value if the current state is Sonicteam::Player::State::TailsFlight.\n    if (pTailsFlight->m_pVftable.ptr == 0x82005404)\n        maturityValue = (1.0f / pTailsContext->m_FlightLimit) * pTailsFlight->m_FlightTime;\n\n    for (int i = 0; i < 4; i++)\n    {\n        if (pGame->m_PlayerData[i].ActorID == pPlayer->m_ActorID.get())\n            pGame->m_PlayerData[i].MaturityValue = maturityValue;\n    }\n\n    __imp__sub_8221A7D8(ctx, base);\n}\n\n// Sonicteam::Player::Score::Score\nPPC_FUNC_IMPL(__imp__sub_821E8C48);\nPPC_FUNC(sub_821E8C48)\n{\n    if (!Config::TailsGauge)\n    {\n        __imp__sub_821E8C48(ctx, base);\n        return;\n    }\n\n    auto pPlayer = (Sonicteam::Player::Object*)(base + ctx.r4.u32);\n\n    if (pPlayer->m_LuaFile == \"player/tails.lua\")\n    {\n        auto pSonicGauge = GuestToHostFunction<Sonicteam::Player::SonicGauge*>(sub_8223F208, g_userHeap.Alloc(sizeof(Sonicteam::Player::SonicGauge)));\n\n        guest_stack_var<boost::shared_ptr<Sonicteam::Player::IGauge>> spSonicGauge;\n\n        // Make shared pointer.\n        GuestToHostFunction<void>(sub_821BEAB0, spSonicGauge.get(), pSonicGauge);\n\n        // Add gauge plugin to player.\n        GuestToHostFunction<void>(sub_821BECE0, pPlayer, spSonicGauge.get(), 1);\n\n        pPlayer->m_spGauge = *spSonicGauge.get();\n    }\n\n    __imp__sub_821E8C48(ctx, base);\n}\n\n// Add missing SetupModuleDebug to table.\nvoid PlayerDebugMode_RegisterLuaSetup(PPCRegister& str, PPCRegister& index)\n{\n    if (!Config::EnableDebugMode)\n        return;\n\n    auto pString = (stdx::string*)g_memory.Translate(str.u32);\n\n    switch (index.u32)\n    {\n        case 0:\n            *pString = \"SetupModuleDebug\";\n            break;\n\n        case 1:\n            *pString = \"SetupModule\";\n            break;\n\n        case 2:\n            *pString = \"SetupModuleDebug\";\n            break;\n    }\n}\n\nbool PlayerDebugMode_RemapDebugExitButton(PPCRegister& r30)\n{\n    auto pPadState = (Sonicteam::SoX::Input::PadState*)g_memory.Translate(r30.u32);\n\n    if (pPadState->IsPressed(Sonicteam::SoX::Input::KeyState_Select))\n    {\n        LOGN(\"Debug Mode: Disabled\");\n        return true;\n    }\n\n    return false;\n}\n\nbool AntigravityRetainsMomentum()\n{\n    return Config::AntigravityRetainsMomentum;\n}\n\nbool ControllableBoundAttack()\n{\n    return Config::ControllableBoundAttack;\n}\n\nbool ControllableBoundAttack2(PPCCRRegister& cmp)\n{\n    if (Config::ControllableBoundAttack)\n        return !cmp.eq;\n\n    return cmp.eq;\n}\n\nbool ControllableSpinkick()\n{\n    return Config::ControllableSpinkick;\n}\n\nbool ControllableTeleportDash()\n{\n    return Config::ControllableTeleportDash;\n}\n\nbool DisablePushState()\n{\n    return Config::DisablePushState;\n}\n\nbool MidairControlForMachSpeed()\n{\n    return Config::MidairControlForMachSpeed;\n}\n\nbool MidairControlForSnowboards()\n{\n    return Config::MidairControlForSnowboards;\n}\n\nvoid RestoreChaosBoostJump(PPCRegister& r10, PPCRegister& r11)\n{\n    if (!Config::RestoreChaosBoostJump)\n        return;\n\n    r10.u32 = 1;\n    r11.u32 = 2;\n}\n\nvoid RestoreChainJumpFlips(PPCRegister& r31, PPCRegister& r30, PPCRegister& r11, PPCRegister& f1, PPCRegister& f2, PPCRegister& f3)\n{\n    if (!Config::RestoreChainJumpFlips)\n        return;\n\n    struct Message\n    {\n        be<uint32_t> ID;\n        Sonicteam::SoX::Math::Quaternion Rotation;\n        Sonicteam::SoX::Math::Vector Position;\n        be<uint32_t> ActorID;\n    };\n\n    auto pPlayer = (Sonicteam::Player::Object*)g_memory.Translate(r31.u32);\n    auto pMessage = (Message*)g_memory.Translate(r30.u32);\n    auto pPlayerContext = (Sonicteam::Player::State::ICommonContext*)g_memory.Translate(r11.u32);\n    auto pActorManager = App::s_pApp->m_pDoc->GetDocMode<Sonicteam::GameMode>()->GetGame()->m_spActorManager.get();\n\n    auto origin = pPlayer->m_spRootFrame->m_PositionF0;\n    auto target = pMessage->Position;\n\n    if (pMessage->ActorID != -1)\n    {\n        auto pFixture = GuestToHostFunction<Sonicteam::Fixture*>(sub_821609D0, pActorManager, &pMessage->ActorID);\n\n        auto msgGetNextPoint = guest_stack_var<Sonicteam::Message::ObjJump123::MsgGetNextPoint>();\n        msgGetNextPoint->Rotation = { 0, 0, 0, 1 };\n        msgGetNextPoint->Position = { 0, 0, 0, 1 };\n\n        if (pFixture->ProcessMessage(msgGetNextPoint.get()))\n            target = msgGetNextPoint->Position;\n    }\n\n    auto magnitudeHorz = f1.f64;\n    auto magnitudeForward = f2.f64;\n    auto distance = origin.DistanceTo(target);\n    auto magnitude = std::sqrt(magnitudeHorz * magnitudeHorz + magnitudeForward * magnitudeForward);\n    auto time = 1.0;\n\n    if (distance > 0.0 && magnitude > 0.0)\n        time = distance / magnitude;\n\n    // CommonContext has a slightly different algorithm to process chain flips.\n    if (((Sonicteam::Player::IPlugIn*)pPlayerContext)->m_pVftable.ptr != 0x8200A728)\n        time *= 0.35;\n\n    f3.f64 = time;\n}\n\nbool RestoreChaosSpearFlips()\n{\n    return Config::RestoreChaosSpearFlips;\n}\n\nbool UnlimitedAntigravity()\n{\n    if (Config::SlidingAttack == ESlidingAttack::B)\n        return true;\n\n    return Config::UnlimitedAntigravity;\n}\n\nPPC_FUNC_IMPL(__imp__sub_82217FC0);\nPPC_FUNC(sub_82217FC0)\n{\n    if (!Config::RestoreSonicActionGauge)\n    {\n        __imp__sub_82217FC0(ctx, base);\n        return;\n    }\n\n    auto pSonicContext = (Sonicteam::Player::State::SonicContext*)(base + ctx.r3.u32);\n    auto pSonicGauge = pSonicContext->m_Gauge.get();\n\n    using enum Sonicteam::Player::State::SonicContext::Gem;\n\n    auto gemIndex = (Sonicteam::Player::State::SonicContext::Gem)ctx.r4.u32;\n\n    switch (gemIndex)\n    {\n        case Gem_Yellow:\n        {\n            // Prevent Yellow Gem spam.\n            if (pSonicContext->m_ThunderGuard)\n                break;\n        }\n\n        case Gem_Blue:\n        case Gem_Green:\n        case Gem_Sky:\n        case Gem_White:\n        case Gem_Super:\n        {\n            auto spriteIndex = Sonicteam::Player::State::SonicContext::ms_GemSpriteConversionTable[gemIndex - 1] - 1;\n\n            if (pSonicGauge->m_Value >= (&pSonicGauge->c_gauge_green)[spriteIndex].get())\n            {\n                ctx.r3.u64 = 1;\n                return;\n            }\n\n            break;\n        }\n\n        case Gem_Red:\n        case Gem_Purple:\n        {\n            if (pSonicContext->m_Field24A == 0)\n            {\n                ctx.r3.u64 = 1;\n                return;\n            }\n\n            break;\n        }\n    }\n\n    ctx.r3.u64 = 0;\n}\n\nPPC_FUNC_IMPL(__imp__sub_82218068);\nPPC_FUNC(sub_82218068)\n{\n    if (!Config::RestoreSonicActionGauge)\n    {\n        __imp__sub_82217FC0(ctx, base);\n        return;\n    }\n\n    auto pSonicContext = (Sonicteam::Player::State::SonicContext*)(base + ctx.r3.u32);\n    auto pSonicGauge = pSonicContext->m_Gauge.get();\n    auto deltaTime = ctx.f1.f64;\n\n    using enum Sonicteam::Player::State::SonicContext::Gem;\n\n    auto gemIndex = (Sonicteam::Player::State::SonicContext::Gem)ctx.r4.u32;\n    auto spriteIndex = Sonicteam::Player::State::SonicContext::ms_GemSpriteConversionTable[gemIndex - 1] - 1;\n\n    switch (gemIndex)\n    {\n        case Gem_Blue:\n        case Gem_Green:\n        case Gem_Yellow:\n        case Gem_Sky:\n        case Gem_White:\n        case Gem_Super:\n        {\n            pSonicGauge->m_Value = pSonicGauge->m_Value.get() - (&pSonicGauge->c_gauge_green)[spriteIndex].get();\n            pSonicGauge->m_GroundedTime = 0.0;\n            break;\n        }\n\n        case Gem_Red:\n        case Gem_Purple:\n        {\n            pSonicGauge->m_Value = pSonicGauge->m_Value.get() - (&pSonicGauge->c_gauge_green)[spriteIndex].get() * deltaTime;\n            pSonicGauge->m_GroundedTime = 0.0;\n\n            if (pSonicGauge->m_Value <= 0)\n            {\n                pSonicGauge->m_Value = 0.0;\n                pSonicContext->m_Shrink = 0;\n                pSonicContext->m_SlowTime = 0;\n                pSonicContext->m_Field24A = 1;\n            }\n\n            break;\n        }\n    }\n}\n\n// Sonicteam::Player::SonicGauge::IVariable::Init\n// This hook redirects the incorrectly named Lua variables to the ones actually used in the scripts.\nPPC_FUNC_IMPL(__imp__sub_8223F360);\nPPC_FUNC(sub_8223F360)\n{\n    auto pIVariable = ctx.r3.u32;\n    auto pLuaSystem = ctx.r4.u32;\n\n    __imp__sub_8223F360(ctx, base);\n\n    if (!Config::RestoreSonicActionGauge)\n        return;\n\n    auto pSonicGauge = (Sonicteam::Player::SonicGauge*)g_memory.Translate(pIVariable - 0x20);\n    auto pVariableName = g_userHeap.Alloc<stdx::string>();\n\n    *pVariableName = \"c_gauge_green\";\n\n    if (pSonicGauge->c_gauge_green == 0.0f)\n        pSonicGauge->c_gauge_green = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_red\";\n\n    if (pSonicGauge->c_gauge_red == 0.0f)\n        pSonicGauge->c_gauge_red = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_blue\";\n\n    if (pSonicGauge->c_gauge_blue == 0.0f)\n        pSonicGauge->c_gauge_blue = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_white\";\n\n    if (pSonicGauge->c_gauge_white == 0.0f)\n        pSonicGauge->c_gauge_white = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_sky\";\n\n    if (pSonicGauge->c_gauge_sky == 0.0f)\n        pSonicGauge->c_gauge_sky = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_yellow\";\n\n    if (pSonicGauge->c_gauge_yellow == 0.0f)\n        pSonicGauge->c_gauge_yellow = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_purple\";\n\n    if (pSonicGauge->c_gauge_purple == 0.0f)\n        pSonicGauge->c_gauge_purple = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    *pVariableName = \"c_gauge_super\";\n\n    if (pSonicGauge->c_gauge_super == 0.0f)\n        pSonicGauge->c_gauge_super = GuestToHostFunction<float>(sub_821EA350, pLuaSystem, pVariableName);\n\n    pVariableName->~string();\n\n    g_userHeap.Free(pVariableName);\n}\n \nvoid SonicGauge_FixGemSprite(PPCRegister& r)\n{\n    auto pGame = (Sonicteam::GameImp*)g_memory.Translate(r.u32);\n\n    for (int i = 0; i < 4; i++)\n        pGame->m_PlayerData[i].GemIndex = 0;\n}\n\nvoid SonicGauge_FixFlags(PPCRegister& r3, PPCRegister& r31)\n{\n    if (!Config::RestoreSonicActionGauge || !r3.u32)\n        return;\n\n    auto pSonicGauge = (Sonicteam::Player::SonicGauge*)g_memory.Translate(r3.u32);\n\n    // Ensure this is SonicGauge.\n    if (((Sonicteam::Player::IPlugIn*)pSonicGauge)->m_pVftable.ptr != 0x8200D4D8)\n        return;\n\n    auto pSonicContext = (Sonicteam::Player::State::SonicContext*)g_memory.Translate(r31.u32);\n    auto pSonicWeapons = pSonicContext->m_spScore->m_pPlayer->GetPlugin<Sonicteam::Player::Weapon::SonicWeapons>(\"sonic_weapons\");\n\n    static constexpr uint8_t s_groundedAnims[4] = { 0xCB, 0xCC, 0x46, 0xCE };\n\n    for (auto i = 0; i < 4; i++)\n    {\n        if (pSonicContext->m_AnimationID == s_groundedAnims[i])\n            pSonicGauge->m_GroundedFlags = 1;\n    }\n\n    if (pSonicContext->m_Tornado != 0 || pSonicWeapons->m_GunDrive.m_pElement)\n    {\n        pSonicGauge->m_GroundedFlags = 1;\n    }\n    else\n    {\n        using enum Sonicteam::Player::State::SonicContext::Gem;\n\n        if ((pSonicContext->m_Buttons.get() & 0x10000) != 0)\n            pSonicContext->m_Field24A = 0;\n\n        if ((pSonicContext->m_Buttons.get() & 0x20000) != 0 && (pSonicContext->m_CurrentGem == Gem_Red || pSonicContext->m_CurrentGem == Gem_Purple))\n        {\n            pSonicGauge->m_GroundedFlags = 1;\n\n            if (pSonicContext->m_Field24A)\n            {\n                pSonicGauge->m_GroundedFlags = 0;\n                pSonicContext->m_Shrink = 0;\n                pSonicContext->m_SlowTime = 0;\n            }\n        }\n        else if ((pSonicContext->m_PostureFlags.get() & Sonicteam::Player::PostureControl::PostureFlag_Grounded) != 0 || pSonicContext->m_Field24A)\n        {\n            pSonicGauge->m_GroundedFlags = 0;\n        }\n    }\n}\n\nbool InfiniteLives()\n{\n    return Config::InfiniteLives;\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/text_patches.cpp",
    "content": "#include \"text_patches.h\"\n#include <api/Marathon.h>\n#include <hid/hid.h>\n#include <kernel/heap.h>\n#include <kernel/memory.h>\n#include <os/logger.h>\n#include <user/config.h>\n\n// Load text card.\nPPC_FUNC_IMPL(__imp__sub_825ECB48);\nPPC_FUNC(sub_825ECB48)\n{\n    auto* pMessage = (const char*)(base + ctx.r5.u32);\n    void* pNewMessage = nullptr;\n\n    for (auto& redirect : TextPatches::s_redirectedMessages)\n    {\n        if (HashStr(pMessage) != redirect.first)\n            continue;\n\n        pNewMessage = g_userHeap.Alloc(strlen(redirect.second) + 1);\n\n        strcpy((char*)pNewMessage, redirect.second);\n\n        ctx.r5.u32 = g_memory.MapVirtual(pNewMessage);\n    }\n\n    __imp__sub_825ECB48(ctx, base);\n\n    if (pNewMessage)\n        g_userHeap.Free(pNewMessage);\n\n    static uint16_t* s_replacementStringPool{};\n    static ELanguage s_replacementStringPoolLanguage{};\n\n    if (s_replacementStringPoolLanguage != Config::Language)\n    {\n        auto length = 0;\n\n        // Compute string pool length.\n        for (auto& replacement : TextPatches::s_replacedMessages)\n            length += (Localise(replacement.second.pKey).length() * 2) + 2;\n\n        if (s_replacementStringPool)\n            g_userHeap.Free(s_replacementStringPool);\n\n        s_replacementStringPool = (uint16_t*)g_userHeap.Alloc(length);\n        s_replacementStringPoolLanguage = Config::Language;\n\n        auto stringPoolPos = s_replacementStringPool;\n\n        for (auto& replacement : TextPatches::s_replacedMessages)\n        {\n            auto& message = Localise(replacement.second.pKey);\n            auto  wideMessage = TransformUTF8ToWString(message);\n            auto  wideMessageLen = wideMessage.length() + 1;\n\n            replacement.second.pGuestText = stringPoolPos;\n\n            for (size_t i = 0; i < wideMessageLen; i++)\n            {\n                *stringPoolPos = ByteSwap(wideMessage.c_str()[i]);\n                stringPoolPos++;\n            }\n        }\n    }\n\n    auto pspTextCard = (boost::shared_ptr<Sonicteam::TextCard>*)(base + ctx.r3.u32);\n    \n    for (auto& replacement : TextPatches::s_replacedMessages)\n    {\n        if (HashStr(pMessage) != replacement.first)\n            continue;\n\n        pspTextCard->get()->m_spResource = boost::make_shared<uint8_t>(0, 0x820334C4);\n        pspTextCard->get()->m_pText = replacement.second.pGuestText;\n        pspTextCard->get()->m_pVariables = (const char*)0x8200139C;\n    }\n}\n\n// Load text book.\nPPC_FUNC_IMPL(__imp__sub_821735B8);\nPPC_FUNC(sub_821735B8)\n{\n    auto* pTextBookPath = (const char*)(base + ctx.r5.u32);\n    void* pNewTextBookPath = nullptr;\n\n    if (Config::IsControllerIconsPS3())\n    {\n        static constexpr const char* MSG_HINT_PS3 = \"text/msg_hint_ps3.mst\";\n\n        if (strcmp(pTextBookPath, \"text/msg_hint_xenon.mst\") == 0)\n        {\n            pNewTextBookPath = g_userHeap.Alloc(strlen(MSG_HINT_PS3) + 1);\n\n            strcpy((char*)pNewTextBookPath, MSG_HINT_PS3);\n\n            ctx.r5.u32 = g_memory.MapVirtual(pNewTextBookPath);\n        }\n    }\n\n    __imp__sub_821735B8(ctx, base);\n\n    if (!pNewTextBookPath)\n        return;\n\n    g_userHeap.Free(pNewTextBookPath);\n}\n\n// Sonicteam::GameImp::OpenHintWindow (speculatory)\nPPC_FUNC_IMPL(__imp__sub_82173838);\nPPC_FUNC(sub_82173838)\n{\n    if (!Config::Hints)\n    {\n        auto pMessage = (const char*)(base + ctx.r4.u32);\n\n        // Block specific hints from volumes.\n        for (auto& pattern : TextPatches::s_hintPatterns)\n        {\n            if (strcmpWildcard(pMessage, pattern))\n            {\n                LOGFN_UTILITY(\"Blocked hint: {}\", pMessage);\n                return;\n            }\n        }\n    }\n\n    __imp__sub_82173838(ctx, base);\n}\n"
  },
  {
    "path": "MarathonRecomp/patches/text_patches.h",
    "content": "#pragma once\n\n#include <xxHashMap.h>\n\nstruct ReplacementMessage\n{\n    const char* pKey{};\n    xpointer<const uint16_t> pGuestText{};\n};\n\nclass TextPatches\n{\npublic:\n    static inline xxHashMap<const char*> s_redirectedMessages\n    {\n        { HashStr(\"msg_deviceselect\"),     \"msg_retry\" },       // Replace \"Select storage device.\" text with \"Retry\" for alert windows.\n        { HashStr(\"msg_gamequitconfirm4\"), \"msg_backtotitle1\" } // Replace \"Exit the game.\" text with \"Go back to the Title Screen.\"\n    };\n\n    static inline xxHashMap<ReplacementMessage> s_replacedMessages\n    {\n        { HashStr(\"msg_goldmedalresults\"),   { \"MainMenu_GoldMedalResults_Name\" } },\n        { HashStr(\"msg_goldmedalresults_c\"), { \"MainMenu_GoldMedalResults_Description\" } }\n    };\n\n    static inline std::vector<const char*> s_hintPatterns =\n    {\n        \"hint_all01_a0*\",\n        \"hint_all01_e06_*\",\n        \"hint_all03_a00_*\",\n        \"hint_all03_h3*\",\n        \"hint_all04_*\",\n        \"hint_all05_a0*\",\n        \"hint_all05_a1*\",\n        \"hint_all05_a2*\",\n        \"hint_all05_a30_*\",\n        \"hint_all05_a31_*\",\n        \"hint_all05_a32_*\",\n        \"hint_all06_*\",\n        \"hint_kdv01_a00_*\",\n        \"hint_kdv01_a02_sv\", // Silver: Man, the staircase is damaged. Now I'll have to use my power.\n        \"hint_kdv01_a04_rg\", // Rouge: You can use this vehicle to get across the lake.\n        \"hint_kdv01_a05_sv\", // Silver: I can manage this cage with my power.\n        \"hint_kdv01_a06_*\",\n        \"hint_kdv01_a07_sv\", // Silver: That container might help me move forward.\n        \"hint_kdv01_a08_sv\", // Silver: I might be able to levitate to that spring.\n        \"hint_kdv01_h*\",\n        \"hint_wvo01_a*\",\n        \"hint_tpj01_a01_*\",\n        \"hint_tpj01_a03_pr\", // Elise: Over there!\n        \"hint_tpj01_a04_*\",\n        \"hint_tpj01_a05_*\",\n        \"hint_tpj01_a06_*\",\n        \"hint_tpj01_a07_sv\", // Silver: This hanging ruin might be useful.\n        \"hint_tpj01_a08_sv\", // Silver: Looks like I can use this to go up.\n        \"hint_tpj01_a09_sv\", // Silver: If I can hit that ball with this...\n        \"hint_tpj01_a10_sv\", // Silver: Is this for breaking walls?\n        \"hint_tpj01_a11_*\",\n        \"hint_tpj01_a12_*\",\n        \"hint_tpj01_a13_*\",\n        \"hint_tpj01_e03_pr\", // Elise: Sonic! To your left!\n        \"hint_tpj01_e05_pr\", // Elise: Sonic! To your right!\n        \"hint_tpj01_e07_rg\", // Rouge: It looks like you need to take them down.\n        \"hint_tpj01_e09_pr\", // Elise: Sonic! Above you!\n        \"hint_tpj01_e10_sn\", // Sonic: There's a route here.\n        \"hint_tpj01_e13_pr\", // Elise: Sonic! Look down!\n        \"hint_tpj01_e18_sn\", // Sonic: Oh? Is there a back road?\n        \"hint_tpj01_e19_sn\", // Sonic: Thank you, Elise!\n        \"hint_tpj01_h*\",\n        \"hint_dtd01_a00_*\",\n        \"hint_dtd01_a02_sd\", // Shadow: It looks like these pillars will lead me to him.\n        \"hint_dtd01_a03_*\",\n        \"hint_dtd01_a04_sv\", // Silver: I might be able to make a path leading up if I use my power over there.\n        \"hint_dtd01_a05_sd\", // Shadow: These big statues look pretty fragile.\n        \"hint_dtd01_h*\",\n        \"hint_wap01_a*\",\n        \"hint_wap01_h*\",\n        \"hint_csc01_a03_sn\", // Sonic: This lamp post... Maybe I can swing off of it?\n        \"hint_csc01_a06_sv\", // Silver: Blaze! Watch out for things flying out of the tornado.\n        \"hint_csc01_a07_sv\", // Silver: This steel bar has a green mark on it. If I use my Psychokinesis here...\n        \"hint_csc01_a08_sv\", // Silver: That road doesn't look too solid.\n        \"hint_csc01_a09_sv\", // Silver: I can wipe them out if I hit that pipe right.\n        \"hint_csc01_a10_bz\", // Blaze: Use your Psychokinesis! You should have no problem with the enemy's bullets!\n        \"hint_csc01_a11_bz\", // Blaze: It looks like you can take advantage of this concrete ramp.\n        \"hint_flc01_a06_sv\", // Silver: I'll have to get closer and use my power directly!\n        \"hint_flc01_h*\",\n        \"hint_rct01_a03_sd\", // Shadow: I'm going to have to destroy the train so it doesn't get away!\n        \"hint_rct01_a04_sv\", // Silver: There's no other way ahead. I guess I've got to break down the door.\n        \"hint_rct01_a05_sv\", // Silver: If I put weights on this scale, I'll be able to move forward.\n        \"hint_rct01_a06_*\",\n        \"hint_rct01_a07_*\",\n        \"hint_rct01_a08_sn\", // Sonic: I'll be able to move faster if I go on top of the train instead of being on the rails.\n        \"hint_rct01_a09_sv\", // Silver: I'll blow away these containers with my power!\n        \"hint_rct01_a10_*\",\n        \"hint_rct01_a12_sd\", // Shadow: I'm going to have to use Homing Missiles so he doesn't get away.\n        \"hint_rct01_h*\",\n        \"hint_aqa01_a00_*\",\n        \"hint_aqa01_a01_tl\", // Tails: If you use the power of this ball, you can get over there.\n        \"hint_aqa01_a02_*\",\n        \"hint_aqa01_a03_tl\", // Tails: If you attack the switch, the magnet will activate.\n        \"hint_aqa01_a04_tl\", // Tails: There are some magnets that repel if you push them twice...\n        \"hint_aqa01_a05_tl\", // Tails: Look! There's the magnet switch. Push it, and the path should open!\n        \"hint_aqa01_a07_sn\", // Sonic: If I slide, I can squeeze through.\n        \"hint_aqa00_e*\",\n        \"hint_end01_e03_*\",\n        \"hint_end01_h*\",\n        \"hint_bos01_a*\",\n        \"hint_bos02_a*\",\n        \"hint_bos03_a*\",\n        \"hint_bos04_a*\",\n        \"hint_bos05_a*\",\n        \"hint_bos06_a*\",\n        \"hint_bos07_a*\",\n        \"hint_bos08_a*\",\n        \"hint_bos09_a*\",\n        \"hint_bos10_a*\",\n        \"hint_bos11_e*\",\n        \"hint_bos11_a*\",\n        \"hint_twn01_a*\",\n        \"hint_twn01_e0?_tl\",\n        \"hint_twn01_e05_kn\", // Knuckles: Heh, there's a secret room here! Let's hurry to the White Acropolis!\n        \"hint_twn01_e0?_pr\",\n        \"hint_twn01_e18_kn\", // Knuckles: Ring three bells at once... Sonic, you should be able to do it!\n        \"hint_twn01_e30_bz\", // Blaze: You need to short out the electricity in order to stop this laser. We'll need to hit the switches at the same time.\n        \"hint_twn01_e43_rg\", // Rouge: The bridge is broken! Shadow, can't you do something?\n        \"hint_twn01_h*\",\n        \"hint_tpj01_e20_pr\"  // Elise: If you jump when the bud is glowing, you may be able to jump higher.\n    };\n};\n"
  },
  {
    "path": "MarathonRecomp/patches/video_patches.cpp",
    "content": "#include <api/Marathon.h>\n#include <kernel/memory.h>\n#include <os/logger.h>\n#include <patches/aspect_ratio_patches.h>\n#include <user/config.h>\n\nconst char* g_pBlockName{};\n\nvoid SetMSAALevel(PPCRegister& val)\n{\n    val.u32 = 0;\n}\n\nvoid BeginBlockGetName(PPCRegister& r3)\n{\n    g_pBlockName = (const char*)g_memory.Translate(r3.u32);\n\n#if _DEBUG\n    if (g_pBlockName)\n        LOGFN_UTILITY(\"Block Begin: {}\", g_pBlockName);\n#endif\n}\n\n// EndBlock\nPPC_FUNC_IMPL(__imp__sub_826078D8);\nPPC_FUNC(sub_826078D8)\n{\n#if _DEBUG\n    if (g_pBlockName)\n        LOGFN_UTILITY(\"Block End: {}\", g_pBlockName);\n#endif\n\n    g_pBlockName = nullptr;\n\n    __imp__sub_826078D8(ctx, base);\n}\n\nfloat ReflectionScaleFactor(EReflectionResolution ref) {\n    switch (ref) {\n        case EReflectionResolution::Eighth:\n            return 0.5f;\n        case EReflectionResolution::Quarter:\n            return 1.0f;\n        case EReflectionResolution::Half:\n            return 2.0f;\n        case EReflectionResolution::Full:\n            return 4.0f;\n        default:\n            return 1.0f;\n    }\n}\n\n// CreateTexture\nPPC_FUNC_IMPL(__imp__sub_82619D00);\nPPC_FUNC(sub_82619D00)\n{\n    auto pName = (stdx::string*)g_memory.Translate(ctx.r4.u32);\n\n    if (*pName == \"radermap\")\n    {\n        ctx.r5.u32 = g_radarMapScale;\n        ctx.r6.u32 = g_radarMapScale;\n    }\n\n    if (*pName == \"reflection0\")\n    {\n        ctx.r5.u32 = static_cast<int>(static_cast<float>(ctx.r5.u32) *\n            ReflectionScaleFactor(Config::ReflectionResolution));\n        ctx.r6.u32 = static_cast<int>(static_cast<float>(ctx.r6.u32) *\n            ReflectionScaleFactor(Config::ReflectionResolution));\n    }\n\n    // RenderMefiress\n    if (*pName == \"user0\")\n    {\n        ctx.r5.u32 = static_cast<int>(Config::ShadowResolution.Value);\n        ctx.r6.u32 = static_cast<int>(Config::ShadowResolution.Value);\n    }\n\n#if _DEBUG\n    auto width = ctx.r5.u32;\n    auto height = ctx.r6.u32;\n#endif\n\n    __imp__sub_82619D00(ctx, base);\n\n#if _DEBUG\n    LOGFN_UTILITY(\"Created texture: {} ({}x{})\", pName->c_str(), width, height);\n#endif\n}\n\n// CreateDepthStencilSurface\nPPC_FUNC_IMPL(__imp__sub_82619B88);\nPPC_FUNC(sub_82619B88)\n{\n    auto pName = (stdx::string*)g_memory.Translate(ctx.r4.u32);\n\n    if (g_pBlockName)\n    {\n        if (strcmp(g_pBlockName, \"radermap0\") == 0)\n        {\n            ctx.r5.u32 = g_radarMapScale;\n            ctx.r6.u32 = g_radarMapScale;\n        }\n\n        // RenderMefiress\n        if (strcmp(g_pBlockName, \"user0\") == 0 && *pName == \"depthstencil_256\")\n        {\n            ctx.r5.u32 = static_cast<int>(Config::ShadowResolution.Value);\n            ctx.r6.u32 = static_cast<int>(Config::ShadowResolution.Value);\n        }\n    }\n\n    if (*pName == \"depthstencil_1_4\")\n    {\n        ctx.r5.u32 = static_cast<int>(static_cast<float>(ctx.r5.u32) *\n            ReflectionScaleFactor(Config::ReflectionResolution));\n        ctx.r6.u32 = static_cast<int>(static_cast<float>(ctx.r6.u32) *\n            ReflectionScaleFactor(Config::ReflectionResolution));\n\n        // Bad hack to stop EDRAM cache from messing up\n        if (Config::ReflectionResolution == EReflectionResolution::Full)\n            ctx.r5.u32++;\n    }\n    \n#if _DEBUG\n    auto width = ctx.r5.u32;\n    auto height = ctx.r6.u32;\n#endif\n\n    __imp__sub_82619B88(ctx, base);\n\n#if _DEBUG\n    if (g_pBlockName)\n    {\n        LOGFN_UTILITY(\"Created texture for {}: {} ({}x{})\", g_pBlockName, pName->c_str(), width, height);\n    }\n    else\n    {\n        LOGFN_UTILITY(\"Created texture: {} ({}x{})\", pName->c_str(), width, height);\n    }\n#endif\n}\n\nfloat ShadowScaleFactor(EShadowResolution ref) {\n    switch (ref) {\n        case EShadowResolution::x512:\n            return 0.5f;\n        case EShadowResolution::x1024:\n            return 1.0f;\n        case EShadowResolution::x2048:\n            return 2.0f;\n        case EShadowResolution::x4096:\n            return 4.0f;\n        case EShadowResolution::x8192:\n            return 8.0f;\n        default:\n            return 1.0f;\n    }\n}\n\n// CreateArrayTexture\nPPC_FUNC_IMPL(__imp__sub_82619FF0);\nPPC_FUNC(sub_82619FF0)\n{\n    auto pName = (stdx::string*)g_memory.Translate(ctx.r4.u32);\n\n    if (*pName == \"csm\")\n    {\n        ctx.r5.u32 = static_cast<int>(static_cast<float>(ctx.r5.u32) *\n            ShadowScaleFactor(Config::ShadowResolution));\n        ctx.r6.u32 = static_cast<int>(static_cast<float>(ctx.r6.u32) *\n            ShadowScaleFactor(Config::ShadowResolution));\n    }\n\n    __imp__sub_82619FF0(ctx, base);\n}\n\nstd::string g_renderWorldFBO;\n\nvoid GetRenderWorldFBO(PPCRegister& name)\n{\n    auto pName = xpointer(reinterpret_cast<char*>(name.u32));\n    g_renderWorldFBO = std::string(pName.get());\n}\n\nvoid FurtherObjectShadows(PPCRegister& scope)\n{\n    if (g_renderWorldFBO != \"shadowmap\")\n        return;\n\n    scope.u32 = 1;\n}\n\nbool DisableRadialBlur()\n{\n    return Config::RadialBlur == ERadialBlur::Off;\n}\n\nbool DisableKingdomValleyMist()\n{\n    return Config::DisableKingdomValleyMist;\n}\n"
  },
  {
    "path": "MarathonRecomp/preload_executable.cpp",
    "content": "#include \"preload_executable.h\"\n#include <os/logger.h>\n\n// Code from Zelda 64: Recompiled\n// https://github.com/Zelda64Recomp/Zelda64Recomp/blob/91db87632c2bfb6995ef1554ec71b11977c621f8/src/main/main.cpp#L440-L514\n\nPreloadContext::~PreloadContext()\n{\n#ifdef _WIN32\n    if (preloaded)\n    {\n        VirtualUnlock(view, size);\n        CloseHandle(mappingHandle);\n        CloseHandle(handle);\n    }\n#endif\n}\n\nvoid PreloadContext::PreloadExecutable()\n{\n#ifdef _WIN32\n    wchar_t moduleName[MAX_PATH];\n    GetModuleFileNameW(NULL, moduleName, MAX_PATH);\n\n    handle = CreateFileW(moduleName, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);\n    if (handle == INVALID_HANDLE_VALUE) \n    {\n        LOG_ERROR(\"Failed to load executable into memory!\");\n        *this = {};\n        return;\n    }\n\n    LARGE_INTEGER moduleSize;\n    if (!GetFileSizeEx(handle, &moduleSize))\n    {\n        LOG_ERROR(\"Failed to get size of executable!\");\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    size = moduleSize.QuadPart;\n\n    mappingHandle = CreateFileMappingW(handle, nullptr, PAGE_READONLY, 0, 0, nullptr);\n    if (mappingHandle == nullptr) \n    {\n        LOG_ERROR(\"Failed to create file mapping of executable!\");\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    view = MapViewOfFile(mappingHandle, FILE_MAP_READ, 0, 0, 0);\n    if (view == nullptr)\n    {\n        LOG_ERROR(\"Failed to map view of of executable!\");\n        CloseHandle(mappingHandle);\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    DWORD pid = GetCurrentProcessId();\n    HANDLE processHandle = OpenProcess(PROCESS_SET_QUOTA | PROCESS_QUERY_INFORMATION, FALSE, pid);\n    if (processHandle == nullptr) \n    {\n        LOG_ERROR(\"Failed to open own process!\");\n        CloseHandle(mappingHandle);\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    SIZE_T minimumSetSize, maximumSetSize;\n    if (!GetProcessWorkingSetSize(processHandle, &minimumSetSize, &maximumSetSize))\n    {\n        LOG_ERROR(\"Failed to get working set size!\");\n        CloseHandle(mappingHandle);\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    if (!SetProcessWorkingSetSize(processHandle, minimumSetSize + size, maximumSetSize + size)) \n    {\n        LOG_ERROR(\"Failed to set working set size!\");\n        CloseHandle(mappingHandle);\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    if (VirtualLock(view, size) == 0) \n    {\n        LOGF_ERROR(\"Failed to lock view of executable! (Error: 0x{:X})\\n\", GetLastError());\n        CloseHandle(mappingHandle);\n        CloseHandle(handle);\n        *this = {};\n        return;\n    }\n\n    preloaded = true;\n#endif\n}\n"
  },
  {
    "path": "MarathonRecomp/preload_executable.h",
    "content": "#pragma once\n\nstruct PreloadContext\n{\n#ifdef _WIN32\n    HANDLE handle{};\n    HANDLE mappingHandle{};\n    SIZE_T size{};\n    PVOID view{};\n    bool preloaded{};\n#endif\n\n    ~PreloadContext();\n    void PreloadExecutable();\n};\n"
  },
  {
    "path": "MarathonRecomp/res/.gitignore",
    "content": "![Ww][Ii][Nn]32/\n*.c\n*.h\n!credits.h"
  },
  {
    "path": "MarathonRecomp/res/credits.h",
    "content": "#pragma once\n\ninline std::array<const char*, 12> g_credits =\n{\n    \"ga2mer\",\n    \"IsaacMarovitz\",\n    \"squidbus\",\n    \"Hyper\",\n    \"Rei-san\",\n    \"Desko\",\n    \"LJSTAR\",\n    \"brianuuuSonic\",\n    \"Kitzuku\",\n    \"Ray Vassos\",\n    \"DaGuAr\",\n    \"NextinHKRY\"\n};\n"
  },
  {
    "path": "MarathonRecomp/res/macos/MacOSXBundleInfo.plist.in",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>English</string>\n\t<key>CFBundleExecutable</key>\n\t<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>\n\t<key>CFBundleGetInfoString</key>\n\t<string>${MACOSX_BUNDLE_INFO_STRING}</string>\n\t<key>CFBundleIconFile</key>\n\t<string>${MACOSX_BUNDLE_ICON_FILE}</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleLongVersionString</key>\n\t<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>\n\t<key>CFBundleName</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>\n\t<key>CFBundlePackageType</key>\n\t<string>APPL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>\n\t<key>CSResourcesFileMapped</key>\n\t<true/>\n\t<key>NSHumanReadableCopyright</key>\n\t<string>${MACOSX_BUNDLE_COPYRIGHT}</string>\n\t<key>LSMinimumSystemVersion</key>\n\t<string>13.0</string>\n\t<key>LSApplicationCategoryType</key>\n\t<string>public.app-category.games</string>\n\t<key>GCSupportsGameMode</key>\n\t<true/>\n\t<key>NSHighResolutionCapable</key>\n\t<true/>\n</dict>\n</plist>"
  },
  {
    "path": "MarathonRecomp/res/version.cpp.template",
    "content": "#include \"version.h\"\n\n// This file is auto-generated, do not modify!\n\nconst char* g_buildType = \"@BUILD_TYPE@\";\n\nconst char* g_branchName = \"@BRANCH_NAME@\";\nconst char* g_commitHash = \"@COMMIT_HASH@\";\nconst char* g_commitHashShort = \"@COMMIT_HASH_SHORT@\";\n\nconst char* g_versionMilestone = \"@VERSION_MILESTONE@\";\nsize_t g_versionMajor = @VERSION_MAJOR@;\nsize_t g_versionMinor = @VERSION_MINOR@;\nsize_t g_versionRevision = @VERSION_REVISION@;\nconst char* g_versionString = \"@VERSION_STRING@\";\n"
  },
  {
    "path": "MarathonRecomp/res/version.h.template",
    "content": "#pragma once\n\n// This file is auto-generated, do not modify!\n\nextern const char* g_buildType;\n\nextern const char* g_branchName;\nextern const char* g_commitHash;\nextern const char* g_commitHashShort;\n\nextern const char* g_versionMilestone;\nextern size_t g_versionMajor;\nextern size_t g_versionMinor;\nextern size_t g_versionRevision;\nextern const char* g_versionString;\n"
  },
  {
    "path": "MarathonRecomp/res/version.txt",
    "content": "VERSION_MILESTONE=\"\"\nVERSION_MAJOR=1\nVERSION_MINOR=0\nVERSION_REVISION=0\n"
  },
  {
    "path": "MarathonRecomp/res/win32/res.rc.template",
    "content": "#include <windows.h>\n\n#define MARATHON_RECOMP_VERSION_BIN @WIN32_VERSION_BINARY@\n#define MARATHON_RECOMP_VERSION_STR \"@WIN32_VERSION_STRING@\"\n\n#ifdef _DEBUG\n#define MARATHON_RECOMP_FILE_FLAGS VS_FF_DEBUG\n#else\n#define MARATHON_RECOMP_FILE_FLAGS 0\n#endif\n\nIDI_ICON1 ICON \"@WIN32_ICON_PATH@\"\n\nVS_VERSION_INFO VERSIONINFO\n    FILEVERSION    MARATHON_RECOMP_VERSION_BIN\n    PRODUCTVERSION MARATHON_RECOMP_VERSION_BIN\n    FILEFLAGSMASK  VS_FFI_FILEFLAGSMASK\n    FILEFLAGS      MARATHON_RECOMP_FILE_FLAGS\n    FILEOS         VOS_NT_WINDOWS32\n    FILETYPE       VFT_APP\n    FILESUBTYPE    VFT2_UNKNOWN\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"080904B0\" // English (UK), Unicode\n        BEGIN\n            VALUE \"ProductName\",      \"Marathon Recompiled\"\n            VALUE \"FileDescription\",  \"Marathon Recompiled\"\n            VALUE \"CompanyName\",      \"sonicnext-dev\"\n            VALUE \"FileVersion\",      MARATHON_RECOMP_VERSION_STR\n            VALUE \"ProductVersion\",   MARATHON_RECOMP_VERSION_STR\n            VALUE \"InternalName\",     \"MarathonRecomp\"\n            VALUE \"OriginalFilename\", \"marathon.exe\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x0809, 0x04B0\n    END\nEND\n"
  },
  {
    "path": "MarathonRecomp/sdl_events.h",
    "content": "#pragma once\n\n#include <SDL.h>\n#include <ui/game_window.h>\n\n#define SDL_USER_PLAYER_CHAR (SDL_USEREVENT + 1)\n\ninline void SDL_ResizeEvent(SDL_Window* pWindow, int width, int height)\n{\n    SDL_Event event{};\n    event.type = SDL_WINDOWEVENT;\n    event.window.event = SDL_WINDOWEVENT_RESIZED;\n    event.window.windowID = SDL_GetWindowID(pWindow);\n    event.window.data1 = width;\n    event.window.data2 = height;\n\n    SDL_PushEvent(&event);\n}\n\ninline void SDL_MoveEvent(SDL_Window* pWindow, int x, int y)\n{\n    SDL_Event event{};\n    event.type = SDL_WINDOWEVENT;\n    event.window.event = SDL_WINDOWEVENT_MOVED;\n    event.window.windowID = SDL_GetWindowID(pWindow);\n    event.window.data1 = x;\n    event.window.data2 = y;\n\n    SDL_PushEvent(&event);\n}\n\ninline void SDL_User_PlayerChar(EPlayerCharacter character)\n{\n    SDL_Event event{};\n    event.type = SDL_USER_PLAYER_CHAR;\n    event.user.code = static_cast<Sint32>(character);\n\n    SDL_PushEvent(&event);\n}\n"
  },
  {
    "path": "MarathonRecomp/sdl_listener.cpp",
    "content": "#include \"sdl_listener.h\"\n\nstd::vector<ISDLEventListener*>& GetEventListeners()\n{\n    static std::vector<ISDLEventListener*> g_eventListeners;\n    return g_eventListeners;\n}\n"
  },
  {
    "path": "MarathonRecomp/sdl_listener.h",
    "content": "#pragma once\n\nclass ISDLEventListener\n{\npublic:\n    virtual ~ISDLEventListener() = default;\n    virtual bool OnSDLEvent(SDL_Event* event) = 0;\n};\n\nextern std::vector<ISDLEventListener*>& GetEventListeners();\n\nclass SDLEventListener : public ISDLEventListener\n{\npublic:\n    SDLEventListener()\n    {\n        GetEventListeners().emplace_back(this);\n    }\n\n    ~SDLEventListener() override\n    {\n        auto& eventListeners = GetEventListeners();\n\n        auto it = std::find(eventListeners.begin(), eventListeners.end(), this);\n\n        assert(it != eventListeners.end());\n\n        eventListeners.erase(it);\n    }\n\n    bool OnSDLEvent(SDL_Event* event) override { return false; }\n};\n"
  },
  {
    "path": "MarathonRecomp/stdafx.cpp",
    "content": "#define STB_IMAGE_IMPLEMENTATION\n#include <stb_image.h>\n\n#include \"stdafx.h\"\n"
  },
  {
    "path": "MarathonRecomp/stdafx.h",
    "content": "#pragma once\n\n#define NOMINMAX\n\n#if defined(_WIN32)\n#include <windows.h>\n#include <ShlObj_core.h>\n#include <wrl/client.h>\n\nusing Microsoft::WRL::ComPtr;\n#elif defined(__linux__)\n#include <unistd.h>\n#include <pwd.h>\n#endif\n\n#ifdef MARATHON_RECOMP_D3D12\n#include <dxcapi.h>\n#endif\n\n#include <algorithm>\n#include <mutex>\n#include <filesystem>\n#include <fstream>\n#include <vector>\n#include <string>\n#include <cassert>\n#include <chrono>\n#include <span>\n#include <xbox.h>\n#include <xxhash.h>\n#include <ankerl/unordered_dense.h>\n#include <ddspp.h>\n#include <ppc/ppc_recomp_shared.h>\n#include <toml++/toml.hpp>\n#include <zstd.h>\n#include <stb_image.h>\n#include <blockingconcurrentqueue.h>\n#include <SDL.h>\n#include <SDL_mixer.h>\n#include <imgui.h>\n#include <imgui_internal.h>\n#include <implot.h>\n#include <backends/imgui_impl_sdl2.h>\n#include <o1heap.h>\n#include <cstddef>\n#include <smolv.h>\n#include <set>\n#include <fmt/core.h>\n#include <list>\n#include <semaphore>\n#include <numeric>\n#include <charconv>\n\n#include \"framework.h\"\n#include \"mutex.h\"\n\n#ifndef _WIN32\n#include <sys/mman.h>\n#endif\n"
  },
  {
    "path": "MarathonRecomp/ui/achievement_menu.cpp",
    "content": "#include \"achievement_menu.h\"\n#include <gpu/imgui/imgui_snapshot.h>\n#include <gpu/video.h>\n#include <hid/hid.h>\n#include <kernel/xdbf.h>\n#include <locale/achievement_locale.h>\n#include <locale/locale.h>\n#include <patches/aspect_ratio_patches.h>\n#include <patches/MainMenuTask_patches.h>\n#include <ui/button_window.h>\n#include <ui/imgui_utils.h>\n#include <user/achievement_manager.h>\n#include <user/config.h>\n#include <app.h>\n#include <exports.h>\n\nstatic constexpr double CONTAINER_MOVE_OFFSET = 0;\nstatic constexpr double CONTAINER_MOVE_DURATION = 5;\nstatic constexpr double CONTAINER_FADE_OFFSET = CONTAINER_MOVE_OFFSET;\nstatic constexpr double CONTAINER_FADE_DURATION = 10;\nstatic constexpr double ROW_FADE_OFFSET = CONTAINER_MOVE_DURATION;\nstatic constexpr double ROW_FADE_TIME = CONTAINER_MOVE_DURATION;\n\nstatic constexpr int MAX_VISIBLE_ROWS = 4;\n\nstatic double g_time{};\nstatic double g_rowSelectionTime{};\nstatic double g_scrollArrowsTime{};\nstatic double g_lastIncrementTime{};\nstatic double g_lastTappedTime{};\n\nstatic bool g_up{};\nstatic bool g_upWasHeld{};\nstatic bool g_down{};\nstatic bool g_downWasHeld{};\nstatic bool g_hasSwitched{};\n\nstatic int g_rowCount{};\nstatic int g_selectedIndex{};\n\nstatic std::vector<std::tuple<Achievement, time_t>> g_achievements{};\n\nstatic void MoveCursor(int& cursorIndex, int min = 0, int max = INT_MAX)\n{\n    auto time = ImGui::GetTime();\n\n    auto scrollUp = g_up;\n    auto scrollDown = g_down;\n\n    if (scrollUp || scrollDown)\n        g_lastTappedTime = time;\n\n    static constexpr auto FAST_SCROLL_THRESHOLD = 0.3;\n    static constexpr auto FAST_SCROLL_SPEED = 1.0 / 6.5;\n\n    auto fastScroll = (time - g_lastTappedTime) > FAST_SCROLL_THRESHOLD;\n\n    if (fastScroll)\n    {\n        if ((time - g_lastIncrementTime) < FAST_SCROLL_SPEED)\n        {\n            fastScroll = false;\n        }\n        else\n        {\n            g_lastIncrementTime = time;\n\n            scrollUp = g_upWasHeld;\n            scrollDown = g_downWasHeld;\n        }\n    }\n\n    if (scrollUp)\n    {\n        --cursorIndex;\n\n        if (cursorIndex < min)\n            cursorIndex = max - 1;\n    }\n    else if (scrollDown)\n    {\n        ++cursorIndex;\n\n        if (cursorIndex >= max)\n            cursorIndex = min;\n    }\n\n    if (scrollUp || scrollDown)\n    {\n        Game_PlaySound(\"move\");\n\n        g_rowSelectionTime = time;\n        g_scrollArrowsTime = g_rowSelectionTime;\n    }\n}\n\nstatic void DrawContainer(ImVec2 min, ImVec2 max)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto containerTopCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 400, 50, 50);\n    auto containerTopCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 400, 50, 50);\n    auto containerSideUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 450, 50, 50);\n    auto containerCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 50, 450, 50, 50);\n    auto containerAlphaMotionTime = AchievementMenu::IsClosing() ? 0 : ComputeLinearMotion(g_time, CONTAINER_FADE_OFFSET, CONTAINER_FADE_DURATION);\n    auto containerAlphaMotion = AchievementMenu::s_state == AchievementMenuState::GoldMedals ? Lerp(63, 0, containerAlphaMotionTime) : 63;\n    auto containerColour = IM_COL32(255, 255, 255, containerAlphaMotion);\n    auto containerEdgeSize = Scale(50, true);\n\n    ImVec2 containerTopLeftCornerMin = min;\n    ImVec2 containerTopLeftCornerMax = { containerTopLeftCornerMin.x + containerEdgeSize, containerTopLeftCornerMin.y + containerEdgeSize };\n    ImVec2 containerTopCentreCornerMin = { containerTopLeftCornerMax.x, containerTopLeftCornerMin.y };\n    ImVec2 containerTopCentreCornerMax = { max.x - containerEdgeSize, containerTopCentreCornerMin.y + containerEdgeSize };\n    ImVec2 containerTopRightCornerMin = { containerTopCentreCornerMax.x, containerTopCentreCornerMin.y };\n    ImVec2 containerTopRightCornerMax = { containerTopRightCornerMin.x + containerEdgeSize, containerTopRightCornerMin.y + containerEdgeSize };\n    ImVec2 containerLeftMin = { containerTopLeftCornerMin.x, containerTopLeftCornerMax.y };\n    ImVec2 containerLeftMax = { containerTopLeftCornerMax.x, max.y };\n    ImVec2 containerRightMin = { containerTopRightCornerMin.x, containerTopRightCornerMax.y };\n    ImVec2 containerRightMax = { containerTopRightCornerMax.x, max.y };\n    ImVec2 containerCentreMin = containerTopLeftCornerMax;\n    ImVec2 containerCentreMax = { containerRightMin.x, containerRightMax.y };\n\n    auto containerBottomFadeStart = Scale(40, true);\n    auto containerBottomFadeEnd = Scale(10, true);\n    ImVec2 containerBottomFadeMin = { containerLeftMin.x, containerLeftMax.y - containerBottomFadeStart };\n    ImVec2 containerBottomFadeMax = { containerRightMax.x, containerRightMax.y + containerBottomFadeEnd };\n\n    SetVerticalGradient(containerBottomFadeMin, containerBottomFadeMax, IM_COL32_WHITE, IM_COL32_WHITE_TRANS);\n    drawList->AddImage(g_upTexMainMenu1.get(), containerTopLeftCornerMin, containerTopLeftCornerMax, GET_UV_COORDS(containerTopCornerUVs), containerColour);\n    drawList->AddImage(g_upTexMainMenu1.get(), containerTopCentreCornerMin, containerTopCentreCornerMax, GET_UV_COORDS(containerTopCentreUVs), containerColour);\n    AddImageFlipped(g_upTexMainMenu1.get(), containerTopRightCornerMin, containerTopRightCornerMax, GET_UV_COORDS(containerTopCornerUVs), containerColour, true);\n    drawList->AddImage(g_upTexMainMenu1.get(), containerLeftMin, containerLeftMax, GET_UV_COORDS(containerSideUVs), containerColour);\n    AddImageFlipped(g_upTexMainMenu1.get(), containerRightMin, containerRightMax, GET_UV_COORDS(containerSideUVs), containerColour, true);\n    drawList->AddImage(g_upTexMainMenu1.get(), containerCentreMin, containerCentreMax, GET_UV_COORDS(containerCentreUVs), containerColour);\n    ResetGradient();\n\n    drawList->PushClipRect(containerTopLeftCornerMin, containerRightMax);\n}\n\nstatic void DrawAchievement(int rowIndex, Achievement& achievement, bool isUnlocked)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto clipRectMin = drawList->GetClipRectMin();\n    auto clipRectMax = drawList->GetClipRectMax();\n\n    auto itemHeight = Scale(94, true);\n    auto itemMarginX = Scale(18, true);\n    auto imageMarginX = Scale(34, true);\n    auto imageMarginY = Scale(20, true);\n    auto imageSize = Scale(64, true);\n\n    auto offsetScroll = 0.0f;\n    auto startIndex = 0;\n\n    // Only scroll if page has more than four items.\n    if (g_rowCount > MAX_VISIBLE_ROWS)\n    {\n        if (g_selectedIndex >= g_rowCount - (MAX_VISIBLE_ROWS - 1))\n        {\n            // Stop scrolling near bottom to use cursor instead.\n            startIndex = g_rowCount - MAX_VISIBLE_ROWS;\n            offsetScroll = -startIndex * itemHeight;\n        }\n        else if (g_selectedIndex >= 1)\n        {\n            // Start scrolling from the middle item.\n            startIndex = g_selectedIndex - 1;\n            offsetScroll = -startIndex * itemHeight;\n        }\n    }\n\n    auto offsetY = itemHeight * rowIndex + offsetScroll;\n    auto isCurrent = g_selectedIndex == rowIndex;\n    auto isVisible = ((rowIndex - startIndex + g_rowCount) % g_rowCount) < MAX_VISIBLE_ROWS;\n\n    if (!isVisible)\n        return;\n\n    ImVec2 min = { clipRectMin.x + itemMarginX, clipRectMin.y + offsetY };\n    ImVec2 max = { clipRectMax.x + itemMarginX, min.y + itemHeight };\n\n    auto rowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 605, 449, 10, 30);\n    auto rowMarginX = Scale(2, true);\n    auto rowMarginY = Scale(24, true);\n\n    auto rowColourBreatheMotionTime = BREATHE_MOTION(1.0f, 0.0f, g_rowSelectionTime, 0.9f);\n    auto rowColourTransitionMotionTime = ComputeLinearMotion(g_time, ROW_FADE_OFFSET, ROW_FADE_TIME);\n\n    constexpr auto rowColourAlphaMax = 165;\n    constexpr auto rowColourAlphaMin = 115;\n\n    auto rowColourAlpha = isCurrent\n        ? Lerp(rowColourAlphaMax, rowColourAlphaMin, rowColourBreatheMotionTime) * rowColourTransitionMotionTime\n        : rowColourAlphaMin * rowColourTransitionMotionTime;\n\n    auto rowColour = IM_COL32(255, 255, 255, rowColourAlpha);\n\n    ImVec2 rowMin = { clipRectMin.x + rowMarginX, min.y + itemHeight - rowMarginY };\n    ImVec2 rowMax = { clipRectMax.x - rowMarginX, rowMin.y + Scale(30, true) };\n\n    SetAdditive(true);\n    SetVerticalGradient(rowMin, rowMax, IM_COL32_WHITE_TRANS, rowColour);\n    drawList->AddImage(g_upTexMainMenu1.get(), rowMin, rowMax, GET_UV_COORDS(rowUVs), rowColour);\n    ResetGradient();\n    ResetAdditive();\n\n    auto image = g_xdbfTextureCache[achievement.ID];\n    ImVec2 imageMin = { min.x + imageMarginX, min.y + imageMarginY };\n    ImVec2 imageMax = { min.x + imageMarginX + imageSize, min.y + imageMarginY + imageSize };\n\n    if (isCurrent)\n        DrawArrowCursor({ rowMin.x + Scale(2, true), imageMin.y + ((imageMax.y - imageMin.y) / 3)}, g_time, false);\n\n    if (!isUnlocked)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_GRAYSCALE);\n\n    // Draw achievement image.\n    drawList->AddImage(image, imageMin, imageMax, { 0, 0 }, { 1, 1 }, IM_COL32_WHITE);\n\n    if (!isUnlocked)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n\n    auto fontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true);\n\n    auto textX = imageMax.x + itemMarginX * 2;\n    auto textColour = isUnlocked ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255);\n\n    std::string name;\n    std::string lockedDesc;\n    std::string unlockedDesc;\n\n    if (Config::UseOfficialAchievementText)\n    {\n        name = achievement.Name;\n        lockedDesc = achievement.LockedDesc;\n        unlockedDesc = achievement.UnlockedDesc;\n    }\n    else\n    {\n        auto& newLocale = GetAchievementLocale(achievement.ID);\n\n        name = newLocale.Name;\n        lockedDesc = newLocale.LockedDesc;\n        unlockedDesc = newLocale.UnlockedDesc;\n    }\n\n    auto descText = isUnlocked ? unlockedDesc.c_str() : lockedDesc.c_str();\n    auto descTextOffsetY = Scale(32, true);\n    auto descSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, descText);\n\n    ImVec2 namePos = { textX, imageMin.y + Scale(3, true) };\n    ImVec2 descPos = { textX, namePos.y + descTextOffsetY };\n\n    // Draw achievement name.\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n    drawList->AddText(g_pFntRodin, fontSize, namePos, textColour, name.c_str());\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n\n    auto timestampOffsetX = Scale(60, true);\n\n    ImVec2 marqueeMin = { textX, min.y };\n    ImVec2 marqueeMax = { max.x - timestampOffsetX, max.y };\n    constexpr auto marqueeDelay = 0.9;\n    auto shouldMarquee = isCurrent && marqueeMin.x + descSize.x >= marqueeMax.x;\n\n    // Reduce clip rect size when marquee starts to make the\n    // scrolling text align more with text above or below it.\n    if (shouldMarquee && ImGui::GetTime() - (g_rowSelectionTime + marqueeDelay) > 0.0)\n    {\n        marqueeMin.x += Scale(2, true);\n        marqueeMax.x -= Scale(1, true);\n    }\n\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n    DrawTextWithMarquee(g_pFntRodin, fontSize, descPos, marqueeMin, marqueeMax, textColour, descText, g_rowSelectionTime, marqueeDelay, shouldMarquee ? Scale(200, true) : 0);\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n\n    if (!isUnlocked)\n        return;\n\n    auto timestamp = AchievementManager::GetTimestamp(achievement.ID);\n\n    if (!timestamp)\n        return;\n\n    char buffer[32];\n\n#ifdef _WIN32\n    tm time{};\n    tm* pTime = &time;\n    localtime_s(pTime, &timestamp);\n#else\n    tm* pTime = localtime(&timestamp);\n#endif\n\n    snprintf(buffer, sizeof(buffer), \"%d/%d/%d %02d:%02d\",\n        pTime->tm_year + 1900, pTime->tm_mon + 1, pTime->tm_mday, pTime->tm_hour, pTime->tm_min);\n    \n    auto timestampSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, buffer);\n\n    // Draw timestamp text.\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n    drawList->AddText(g_pFntRodin, fontSize, { max.x - timestampSize.x - timestampOffsetX, namePos.y }, IM_COL32_WHITE, buffer);\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n}\n\nvoid AchievementMenu::Draw()\n{\n    if (AchievementMenu::IsClosing() && s_commonMenu.Close() >= 1.0)\n    {\n        s_isVisible = false;\n        return;\n    }\n\n    if (!s_isVisible)\n        return;\n\n    auto* drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    if (s_commonMenu.ShowDescription)\n    {\n        auto horzMargin = Scale(128, true);\n        auto gradientTop = IM_COL32(0, 103, 255, 255);\n        auto gradientBottom = IM_COL32(0, 41, 100, 255);\n\n        ImVec2 footerClipMin = { g_horzCentre + horzMargin, res.y - g_vertCentre - Scale(152, true) };\n        ImVec2 footerClipMax = { res.x - g_horzCentre - horzMargin, res.y - g_vertCentre - Scale(107, true) };\n\n        drawList->PushClipRect(footerClipMin, footerClipMax);\n        drawList->AddRectFilledMultiColor({ 0.0f, g_vertCentre }, { res.x, res.y - g_vertCentre }, gradientTop, gradientTop, gradientBottom, gradientBottom);\n        drawList->PopClipRect();\n    }\n\n    s_commonMenu.Draw();\n\n    if (s_pMainMenuTask && s_pMainMenuTask->m_State == Sonicteam::MainMenuTask::MainMenuState_GoldMedalResults)\n    {\n        if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0])\n        {\n            auto& rPadState = spInputManager->m_PadState;\n\n            if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B))\n            {\n                AchievementMenu::Close();\n                ButtonWindow::Close();\n            }\n\n            switch (AchievementMenu::s_state)\n            {\n                case AchievementMenuState::GoldMedals:\n                {\n                    if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_Y))\n                    {\n                        AchievementMenu::SetState(AchievementMenuState::Achievements);\n                        ButtonWindow::Open(\"Button_GoldMedalsBack\");\n                        SetGoldMedalResultsVisible(false);\n                        Game_PlaySound(\"window_open\");\n                        g_hasSwitched = true;\n                    }\n\n                    break;\n                }\n\n                case AchievementMenuState::Achievements:\n                {\n                    // Discard all buttons besides B.\n                    if ((s_pMainMenuTask->m_PressedButtons.get() & 0x20) == 0)\n                        s_pMainMenuTask->m_PressedButtons = 0;\n\n                    if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_Y))\n                    {\n                        AchievementMenu::SetState(AchievementMenuState::GoldMedals);\n                        ButtonWindow::Open(\"Button_AchievementsBack\");\n                        SetGoldMedalResultsVisible(true);\n                        Game_PlaySound(\"window_close\");\n                    }\n\n                    break;\n                }\n            }\n        }\n    }\n\n    auto containerMotionTime = ComputeLinearMotion(g_time, CONTAINER_MOVE_OFFSET, CONTAINER_MOVE_DURATION, s_state == AchievementMenuState::GoldMedals);\n    auto containerTop = Scale(Lerp(217, 148, containerMotionTime), true);\n    auto containerBottom = Scale(554, true);\n    auto containerWidth = Scale(1158, true);\n\n    ImVec2 min = { (res.x / 2) - containerWidth / 2, g_vertCentre + containerTop };\n    ImVec2 max = { min.x + containerWidth, g_vertCentre + containerBottom };\n\n    switch (s_state)\n    {\n        case AchievementMenuState::GoldMedals:\n        {\n            s_commonMenu.SetTitle(Localise(\"Achievements_GoldMedals_Uppercase\"));\n            s_commonMenu.ShowDescription = false;\n            \n            if (g_hasSwitched)\n            {\n                // For outro animation.\n                DrawContainer(min, max);\n                drawList->PopClipRect();\n            }\n\n            break;\n        }\n\n        case AchievementMenuState::Achievements:\n        {\n            s_commonMenu.SetTitle(Localise(\"Achievements_Title_Uppercase\"));\n            s_commonMenu.ShowDescription = true;\n\n            DrawContainer(min, max);\n\n            if (containerMotionTime >= 1.0)\n            {\n                auto rowIndex = 0;\n\n                for (auto& tpl : g_achievements)\n                {\n                    auto& achievement = std::get<0>(tpl);\n\n                    if (AchievementManager::IsUnlocked(achievement.ID))\n                        DrawAchievement(rowIndex++, achievement, true);\n                }\n\n                for (auto& tpl : g_achievements)\n                {\n                    auto& achievement = std::get<0>(tpl);\n\n                    if (!AchievementManager::IsUnlocked(achievement.ID))\n                        DrawAchievement(rowIndex++, achievement, false);\n                }\n\n                drawList->PopClipRect();\n\n                auto scrollArrowsOffsetX = Scale(17, true);\n                auto scrollArrowsOffsetTop = Scale(40, true);\n                auto scrollArrowsOffsetBottom = Scale(60, true);\n                ImVec2 scrollArrowsMin = { max.x + scrollArrowsOffsetX, min.y + scrollArrowsOffsetTop };\n                ImVec2 scrollArrowsMax = { scrollArrowsMin.x + scrollArrowsOffsetX, max.y - scrollArrowsOffsetBottom };\n\n                DrawScrollArrows(scrollArrowsMin, scrollArrowsMax, Scale(25, true), g_scrollArrowsTime, g_selectedIndex > MAX_VISIBLE_ROWS / 2, g_selectedIndex < g_rowCount - MAX_VISIBLE_ROWS / 2);\n\n                auto upIsHeld = false;\n                auto downIsHeld = false;\n\n                for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager)\n                {\n                    auto& rPadState = spInputManager->m_PadState;\n\n                    if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadUp) || -rPadState.LeftStickVertical > 0.5f)\n                        upIsHeld = true;\n\n                    if (!g_upWasHeld && upIsHeld)\n                        g_up = true;\n\n                    if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadDown) || -rPadState.LeftStickVertical < -0.5f)\n                        downIsHeld = true;\n\n                    if (!g_downWasHeld && downIsHeld)\n                        g_down = true;\n                }\n\n                MoveCursor(g_selectedIndex, 0, g_rowCount);\n\n                g_up = false;\n                g_upWasHeld = upIsHeld;\n                g_down = false;\n                g_downWasHeld = downIsHeld;\n            }\n\n            drawList->PopClipRect();\n\n            break;\n        }\n    }\n}\n\nvoid AchievementMenu::Open(Sonicteam::MainMenuTask* pMainMenuTask)\n{\n    s_pMainMenuTask = pMainMenuTask;\n\n    if (s_isVisible)\n        return;\n\n    g_achievements.clear();\n\n    for (auto& achievement : g_xdbfWrapper.GetAchievements((EXDBFLanguage)Config::Language.Value))\n    {\n        achievement.LockedDesc = xdbf::FixInvalidSequences(achievement.LockedDesc);\n\n        g_achievements.push_back(std::make_tuple(achievement, AchievementManager::GetTimestamp(achievement.ID)));\n    }\n\n    std::sort(g_achievements.begin(), g_achievements.end(), [](const auto& a, const auto& b)\n    {\n        return std::get<1>(a) > std::get<1>(b);\n    });\n\n    g_rowCount = g_achievements.size();\n\n    // Format achievement progress.\n    char descriptionText[128];\n    snprintf(descriptionText, sizeof(descriptionText), Localise(\"Achievements_Progress\").c_str(), AchievementManager::GetTotalRecords(), g_rowCount);\n\n    s_commonMenu = CommonMenu(Localise(\"Achievements_GoldMedals_Uppercase\"), descriptionText, false);\n    s_commonMenu.ShowDescription = false;\n    s_commonMenu.ShowVersionString = false;\n    s_commonMenu.ReduceDraw = true;\n    s_commonMenu.Open();\n\n    s_isVisible = true;\n    s_state = AchievementMenuState::GoldMedals;\n    g_time = ImGui::GetTime();\n    g_hasSwitched = false;\n    g_selectedIndex = 0;\n\n    ButtonWindow::Open(\"Button_AchievementsBack\");\n    MainMenuTaskPatches::s_hideButtonWindow = true;\n}\n\nvoid AchievementMenu::Close()\n{\n    if (AchievementMenu::IsClosing())\n        return;\n\n    switch (s_state)\n    {\n        case AchievementMenuState::GoldMedals:\n            s_state = AchievementMenuState::ClosingGoldMedals;\n            break;\n\n        case AchievementMenuState::Achievements:\n            s_state = AchievementMenuState::ClosingAchievements;\n            break;\n    }\n\n    g_time = ImGui::GetTime();\n\n    ButtonWindow::Close();\n    MainMenuTaskPatches::s_hideButtonWindow = false;\n}\n\nvoid AchievementMenu::SetState(AchievementMenuState state)\n{\n    s_state = state;\n    g_time = ImGui::GetTime();\n    g_rowSelectionTime = g_time;\n}\n\nbool AchievementMenu::IsClosing()\n{\n    return s_state == AchievementMenuState::ClosingGoldMedals || s_state == AchievementMenuState::ClosingAchievements;\n}\n\nvoid AchievementMenu::SetGoldMedalResultsVisible(bool isVisible)\n{\n    if (!s_pMainMenuTask)\n        return;\n\n    std::vector<int> states;\n\n    if (isVisible)\n    {\n        states = { 0, 3, 5, 11 };\n    }\n    else\n    {\n        states = { 1, 4, 6, 12 };\n    }\n\n    for (auto& state : states)\n    {\n        guest_stack_var<Sonicteam::Message::HUDGoldMedal::MsgChangeState> msgChangeState(state, s_pMainMenuTask->m_GoldMedalEpisodeIndex);\n        s_pMainMenuTask->m_pHUDGoldMedal->ProcessMessage(msgChangeState.get());\n    }\n\n    for (int i = 0; i < 5; i++)\n    {\n        auto& spTextEntity = s_pMainMenuTask->m_pHUDGoldMedal->m_aspTextEntities[i];\n\n        if (auto pTextEntity = spTextEntity.get())\n        {\n            for (size_t i = 0; i < pTextEntity->m_CharacterVertexCount; i++)\n                pTextEntity->m_pCharacterVertices[i].Colour = isVisible ? 0xFFFFFFFF : 0x00FFFFFF;\n        }\n    }\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/achievement_menu.h",
    "content": "#pragma once\n\n#include <api/Marathon.h>\n#include <ui/common_menu.h>\n\n#define MARATHON_RECOMP_ACHIEVEMENT_MENU\n\nenum class AchievementMenuState\n{\n    GoldMedals,\n    Achievements,\n    ClosingGoldMedals,\n    ClosingAchievements\n};\n\nclass AchievementMenu\n{\npublic:\n    static inline CommonMenu s_commonMenu{};\n    static inline AchievementMenuState s_state{};\n    static inline Sonicteam::MainMenuTask* s_pMainMenuTask{};\n    static inline bool s_isVisible = false;\n\n    static void Draw();\n    static void Open(Sonicteam::MainMenuTask* pMainMenuTask);\n    static void Close();\n    static void SetState(AchievementMenuState state);\n    static bool IsClosing();\n    static void SetGoldMedalResultsVisible(bool isVisible);\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/achievement_overlay.cpp",
    "content": "#include \"achievement_overlay.h\"\n#include <kernel/memory.h>\n#include <kernel/xdbf.h>\n#include <locale/achievement_locale.h>\n#include <locale/locale.h>\n#include <ui/imgui_utils.h>\n#include <user/achievement_data.h>\n#include <user/config.h>\n#include <app.h>\n#include <exports.h>\n\nconstexpr double OVERLAY_DURATION = 5.0;\n\nstatic bool g_isClosing{};\nstatic double g_appearTime{};\n\nstatic Achievement g_achievement{};\n\n// Dequeue achievements only in the main thread. This is also extra thread safety.\nstatic std::thread::id g_mainThreadId = std::this_thread::get_id();\n\nstatic bool CanDequeueAchievement()\n{\n    return std::this_thread::get_id() == g_mainThreadId && !AchievementOverlay::s_queue.empty();\n}\n\nvoid AchievementOverlay::Draw()\n{\n    if (!AchievementOverlay::s_isVisible && CanDequeueAchievement())\n    {\n        s_isVisible = true;\n        g_isClosing = false;\n        g_appearTime = ImGui::GetTime();\n        g_achievement = g_xdbfWrapper.GetAchievement((EXDBFLanguage)Config::Language.Value, s_queue.front());\n        s_queue.pop();\n\n        Game_PlaySound(\"deside\");\n    }\n    \n    if (!s_isVisible)\n        return;\n    \n    if (ImGui::GetTime() - g_appearTime >= OVERLAY_DURATION)\n        AchievementOverlay::Close();\n\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    auto& strAchievementUnlocked = Localise(\"Achievements_Unlock\");\n    auto& strAchievementName = g_achievement.Name;\n\n    if (!Config::UseOfficialAchievementText)\n        strAchievementName = GetAchievementLocale(g_achievement.ID).Name;\n\n    // Calculate text sizes.\n    auto fontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true);\n    auto headerSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementUnlocked.c_str());\n    auto nameSize = g_pFntRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, strAchievementName.c_str());\n    auto nameOffsetY = Scale(6);\n    auto maxWidth = std::max(headerSize.x, nameSize.x) + Scale(5);\n    auto maxHeight = headerSize.y + nameSize.y + nameOffsetY;\n\n    // Calculate margins.\n    auto imageMarginX = Scale(25);\n    auto imageMarginY = Scale(20);\n    auto imageSize = Scale(64);\n    auto textMarginX = imageMarginX * 2 + imageSize - Scale(5);\n    auto containerWidth = imageMarginX + textMarginX + maxWidth;\n    auto containerHeight = Scale(105);\n\n    ImVec2 min = { (res.x / 2) - (containerWidth / 2), Scale(55) };\n    ImVec2 max = { min.x + containerWidth, min.y + containerHeight };\n\n    // Calculate centred vertical text offset.\n    auto textMarginY = (min.y + (containerHeight / 2)) - (maxHeight / 2);\n\n    auto windowMotion = DrawWindow(min, max, true, g_appearTime, g_isClosing);\n\n    if (windowMotion >= 1.0)\n    {\n        // Draw achievement icon.\n        drawList->AddImage\n        (\n            g_xdbfTextureCache[g_achievement.ID],\n            { /* X */ min.x + imageMarginX, /* Y */ min.y + imageMarginY },\n            { /* X */ min.x + imageMarginX + imageSize, /* Y */ min.y + imageMarginY + imageSize },\n            { 0, 0 },\n            { 1, 1 },\n            IM_COL32(255, 255, 255, 255)\n        );\n\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n        auto colourMotion = BREATHE_MOTION(1.0f, 0.0f, g_appearTime, 0.9f);\n        auto headerColour = ColourLerp(IM_COL32_WHITE, IM_COL32(255, 153, 0, 255), colourMotion);\n\n        // Draw header text.\n        drawList->AddText\n        (\n            g_pFntRodin,\n            fontSize,\n            { /* X */ min.x + textMarginX + (maxWidth - headerSize.x) / 2, /* Y */ textMarginY },\n            headerColour,\n            strAchievementUnlocked.c_str()\n        );\n\n        // Draw achievement name.\n        drawList->AddText\n        (\n            g_pFntRodin,\n            fontSize,\n            { /* X */ min.x + textMarginX + (maxWidth - nameSize.x) / 2, /* Y */ textMarginY + nameSize.y + nameOffsetY },\n            IM_COL32(255, 255, 255, 255),\n            strAchievementName.c_str()\n        );\n\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n    }\n    else if (windowMotion <= 0.0 && g_isClosing)\n    {\n        s_isVisible = false;\n    }\n\n    drawList->PopClipRect();\n}\n\nvoid AchievementOverlay::Open(int id)\n{\n    s_queue.push(id);\n}\n\nvoid AchievementOverlay::Close()\n{\n    if (!g_isClosing)\n    {\n        g_appearTime = ImGui::GetTime();\n        g_isClosing = true;\n    }\n\n    if (CanDequeueAchievement())\n        s_isVisible = false;\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/achievement_overlay.h",
    "content": "#pragma once\n\n#include <queue>\n\nclass AchievementOverlay\n{\npublic:\n    static inline bool s_isVisible = false;\n\n    static inline std::queue<uint16_t> s_queue{};\n\n    static void Draw();\n    static void Open(int id);\n    static void Close();\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/black_bar.cpp",
    "content": "#include \"black_bar.h\"\n#include <patches/aspect_ratio_patches.h>\n#include <ui/imgui_utils.h>\n#include <app.h>\n\nstatic bool g_isVisible{};\nstatic bool g_isEdgeFade{};\n\nvoid BlackBar::Draw()\n{\n    s_pillarboxWidth = std::max(0.0f, (Video::s_viewportWidth - (Video::s_viewportHeight * WIDE_ASPECT_RATIO)) / 2.0f);\n    s_letterboxHeight = std::max(0.0f, (Video::s_viewportHeight - (Video::s_viewportWidth / WIDE_ASPECT_RATIO)) / 2.0f);\n\n    if (!g_isVisible)\n        return;\n\n    auto* drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    auto fadeSize = Scale(50, true);\n\n    if (g_aspectRatio > WIDE_ASPECT_RATIO)\n    {\n        ImVec2 leftPillarboxMin = { 0, 0 };\n        ImVec2 leftPillarboxMax = { s_pillarboxWidth - s_margin, res.y };\n        ImVec2 rightPillarboxMin = { (res.x - s_pillarboxWidth) + s_margin, 0 };\n        ImVec2 rightPillarboxMax = { res.x, res.y };\n\n        if (g_isEdgeFade)\n            SetHorizontalGradient({ leftPillarboxMax.x - fadeSize, leftPillarboxMin.y }, leftPillarboxMax, IM_COL32_BLACK, IM_COL32_BLACK_TRANS);\n\n        drawList->AddRectFilled(leftPillarboxMin, leftPillarboxMax, IM_COL32_BLACK);\n\n        if (g_isEdgeFade)\n            SetHorizontalGradient(rightPillarboxMin, { rightPillarboxMin.x + fadeSize, rightPillarboxMax.y }, IM_COL32_BLACK_TRANS, IM_COL32_BLACK);\n\n        drawList->AddRectFilled(rightPillarboxMin, rightPillarboxMax, IM_COL32_BLACK);\n    }\n    else if (WIDE_ASPECT_RATIO > g_aspectRatio)\n    {\n        ImVec2 topLetterboxMin = { 0, 0 };\n        ImVec2 topLetterboxMax = { res.x, s_letterboxHeight - s_margin };\n        ImVec2 bottomLetterboxMin = { 0, res.y - s_letterboxHeight + s_margin };\n        ImVec2 bottomLetterboxMax = { res.x, res.y };\n\n        if (g_isEdgeFade)\n            SetVerticalGradient({ topLetterboxMin.x, topLetterboxMax.y - fadeSize }, topLetterboxMax, IM_COL32_BLACK, IM_COL32_BLACK_TRANS);\n\n        drawList->AddRectFilled(topLetterboxMin, topLetterboxMax, IM_COL32_BLACK);\n\n        if (g_isEdgeFade)\n            SetVerticalGradient(bottomLetterboxMin, { bottomLetterboxMax.x, bottomLetterboxMin.y + fadeSize }, IM_COL32_BLACK_TRANS, IM_COL32_BLACK);\n\n        drawList->AddRectFilled(bottomLetterboxMin, bottomLetterboxMax, IM_COL32_BLACK);\n    }\n\n    if (g_isEdgeFade)\n        ResetGradient();\n\n    if (App::s_isLoading)\n        return;\n\n    g_isVisible = false;\n    g_isEdgeFade = false;\n    s_margin = 0.0f;\n}\n\nvoid BlackBar::Show(bool isEdgeFade)\n{\n    g_isVisible = true;\n    g_isEdgeFade = isEdgeFade;\n}\n\nvoid BlackBar::Hide()\n{\n    g_isVisible = false;\n}\n\nvoid BlackBar::SetBorderMargin(float margin)\n{\n    s_margin = margin;\n}\n\nbool BlackBar::IsVisible()\n{\n    return g_isVisible;\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/black_bar.h",
    "content": "#pragma once\n\nclass BlackBar\n{\npublic:\n    static constexpr double ms_MenuBorderMargin = 70.0;\n\n    static inline float s_pillarboxWidth{};\n    static inline float s_letterboxHeight{};\n    static inline float s_margin{};\n\n    static void Draw();\n    static void Show(bool isEdgeFade = false);\n    static void Hide();\n    static void SetBorderMargin(float margin);\n    static bool IsVisible();\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/button_window.cpp",
    "content": "#include \"button_window.h\"\n#include <patches/aspect_ratio_patches.h>\n#include <ui/imgui_utils.h>\n#include <app.h>\n\nstatic std::string g_buttonKey{};\nstatic double g_time{};\nstatic bool g_isAnimated{};\n\nvoid ButtonWindow::Draw()\n{\n    if (!s_isVisible || g_buttonKey.empty())\n        return;\n\n    auto* drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    auto interpData = GetHidInterpTextData();\n    auto fontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true);\n\n    auto buttonLocale = &Localise(g_buttonKey);\n\n    auto windowEdgeUVs = PIXELS_TO_UV_COORDS(64, 64, 1, 0, 40, 64);\n    auto windowStretchUVs = PIXELS_TO_UV_COORDS(64, 64, 40, 0, 23, 64);\n    auto windowOffsetX = g_horzCentre + Scale(128, true);\n    auto windowOffsetY = g_vertCentre + Scale(114.5, true);\n    auto windowEdgeWidth = Scale(40, true);\n    auto windowWidth = MeasureInterpolatedText(g_pFntRodin, fontSize, buttonLocale->c_str(), &interpData).x;\n    auto windowHeight = Scale(64, true);\n    auto windowColour = IM_COL32(255, 255, 255, 134);\n\n    auto windowMotionTime = g_isAnimated ? ComputeLinearMotion(g_time, 0, 100) : 1.0;\n    auto windowMotion = std::clamp(float(res.x - g_horzCentre - Scale(1158.3, true) * windowMotionTime), res.x - windowOffsetX - windowWidth, res.x - g_horzCentre);\n\n    ImVec2 windowStretchMin = { windowMotion, res.y - windowOffsetY };\n    ImVec2 windowStretchMax = { res.x, windowStretchMin.y + windowHeight };\n    ImVec2 windowEdgeMin = { windowStretchMin.x - windowEdgeWidth, windowStretchMin.y };\n    ImVec2 windowEdgeMax = { windowEdgeMin.x + windowEdgeWidth, windowEdgeMin.y + windowHeight };\n\n    drawList->AddImage(g_upTexButtonWindow.get(), windowStretchMin, windowStretchMax, GET_UV_COORDS(windowStretchUVs), windowColour);\n    drawList->AddImage(g_upTexButtonWindow.get(), windowEdgeMin, windowEdgeMax, GET_UV_COORDS(windowEdgeUVs), windowColour);\n\n    ImVec2 textPos = { windowEdgeMax.x - Scale(7.5, true), windowEdgeMin.y + Scale(12.75, true) };\n\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n    DrawInterpolatedText(g_pFntRodin, fontSize, textPos, IM_COL32_WHITE, buttonLocale->c_str(), &interpData);\n\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n}\n\nvoid ButtonWindow::Open(std::string key, bool isAnimated)\n{\n    s_isVisible = true;\n\n    if (g_buttonKey != key)\n    {\n        g_buttonKey = key;\n        g_time = ImGui::GetTime();\n        g_isAnimated = isAnimated;\n    }\n}\n\nvoid ButtonWindow::Close()\n{\n    s_isVisible = false;\n    g_buttonKey.clear();\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/button_window.h",
    "content": "#pragma once\n\nclass ButtonWindow\n{\npublic:\n    static inline bool s_isVisible = false;\n\n    static void Draw();\n    static void Open(std::string key, bool isAnimated = true);\n    static void Close();\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/common_menu.cpp",
    "content": "#include \"common_menu.h\"\n#include <patches/aspect_ratio_patches.h>\n#include <ui/black_bar.h>\n#include <ui/imgui_utils.h>\n#include <app.h>\n\nstatic constexpr double ANIMATION_DURATION = 10.0;\n\nvoid CommonMenu::Draw()\n{\n    auto* drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n    ImVec2 min = { g_horzCentre, g_vertCentre };\n    ImVec2 max = { res.x - min.x, res.y - min.y };\n\n    auto borderMotionTime = PlayTransitions ? ComputeLinearMotion(m_time, 0, 10, m_isClosing) : 1.0;\n\n    auto topPlateCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 154, 250, 144);\n    auto topPlateLeftStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 2, 154, 150, 144);\n    auto topPlateRightStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 250, 154, 750, 144);\n    auto topPlateHeight = Scale(145, true);\n    auto topPlateMotion = Lerp(min.y - topPlateHeight, min.y - Scale(0.2, true), borderMotionTime);\n\n    ImVec2 topPlateCornerMin = { min.x - Scale(46, true), topPlateMotion };\n    ImVec2 topPlateCornerMax = { topPlateCornerMin.x + Scale(250, true), topPlateMotion + topPlateHeight };\n    ImVec2 topPlateStretchMin = { topPlateCornerMax.x, topPlateCornerMin.y };\n    ImVec2 topPlateStretchMax = { res.x, topPlateCornerMax.y };\n\n    auto redStripCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 301, 300, 50);\n    auto redStripLeftStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 0, 301, 150, 50);\n    auto redStripRightStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 300, 301, 300, 50);\n    auto redStripColour = IM_COL32(168, 15, 15, 255);\n    auto redStripOffsetY = Scale(81.5, true);\n    auto redStripHeight = Scale(50, true);\n    auto redStripMotion = topPlateMotion + redStripOffsetY;\n\n    // TODO: account for metal plate height in motion.\n    ImVec2 redStripCornerMin = { min.x - Scale(43.5, true), redStripMotion };\n    ImVec2 redStripCornerMax = { redStripCornerMin.x + Scale(300, true), redStripMotion + redStripHeight };\n\n    auto gradientTop = IM_COL32(0, 103, 255, 255);\n    auto gradientBottom = IM_COL32(0, 92, 229, 255);\n    auto gradientHeight = Scale(120, true);\n\n    // Draw gradient to fill gap between red strip and top metal plates.\n    if (!ReduceDraw)\n        drawList->AddRectFilledMultiColor({ 0.0f, topPlateMotion }, { res.x, topPlateMotion + gradientHeight }, gradientTop, gradientTop, gradientBottom, gradientBottom);\n\n    if (!ReduceDraw)\n    {\n        // Draw corner left red strip.\n        drawList->AddImage(g_upTexMainMenu1.get(), redStripCornerMin, redStripCornerMax, GET_UV_COORDS(redStripCornerUVs), redStripColour);\n\n        // Draw left stretched red strip.\n        drawList->AddImage(g_upTexMainMenu1.get(), { 0.0f, redStripCornerMin.y }, { redStripCornerMin.x, redStripCornerMax.y }, GET_UV_COORDS(redStripLeftStretchUVs), redStripColour);\n\n        // Draw right stretched red strip.\n        drawList->AddImage(g_upTexMainMenu1.get(), { redStripCornerMax.x, redStripCornerMin.y }, { res.x, redStripCornerMax.y }, GET_UV_COORDS(redStripRightStretchUVs), redStripColour);\n\n        auto redStripHighlightWidth = Scale(270, true);\n        auto redStripHighlightHeight = Scale(48, true);\n        auto redStripHighlightY = redStripCornerMin.y - Scale(3, true);\n\n        auto drawRedStripHighlight = [&](ImVec2 redStripHighlightMin, uint32_t tlColour, uint32_t brColour)\n        {\n            auto redStripHighlightUVs = PIXELS_TO_UV_COORDS(1024, 1024, 201, 501, 264, 50);\n\n            ImVec2 redStripHighlightMax = { redStripHighlightMin.x + redStripHighlightWidth, redStripHighlightMin.y + redStripHighlightHeight };\n\n            SetAdditive(true);\n            SetHorizontalGradient(redStripHighlightMin, redStripHighlightMax, tlColour, brColour);\n\n            drawList->AddImage(g_upTexMainMenu1.get(), redStripHighlightMin, redStripHighlightMax, GET_UV_COORDS(redStripHighlightUVs));\n\n            ResetGradient();\n            ResetAdditive();\n        };\n\n        // Draw fixed highlights for red strip.\n        drawRedStripHighlight({ redStripCornerMax.x + Scale(68, true), redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, 67));\n        drawRedStripHighlight({ redStripCornerMax.x + Scale(-43.5, true), redStripHighlightY }, IM_COL32(255, 89, 0, 0), IM_COL32(255, 89, 0, 20));\n\n        auto redStripHighlightMotionOffsetX = Scale(63, true);\n        auto redStripHighlightMotionX1Time = ComputeLinearMotion(m_titleTime, PlayTransitions ? 10 : 0, 10, m_isClosing);\n        auto redStripHighlightMotionX2Time = ComputeLinearMotion(m_titleTime, PlayTransitions ? 12 : 2, 10, m_isClosing);\n        auto redStripHighlightMotionAlphaIn1Time = ComputeLinearMotion(m_titleTime, PlayTransitions ? 10 : 0, 8, m_isClosing);\n        auto redStripHighlightMotionAlphaIn2Time = ComputeLinearMotion(m_titleTime, PlayTransitions ? 12 : 2, 8, m_isClosing);\n        auto redStripHighlightMotionAlphaOut1Time = ComputeLinearMotion(m_titleTime, PlayTransitions ? 18 : 8, 2, m_isClosing);\n        auto redStripHighlightMotionAlphaOut2Time = ComputeLinearMotion(m_titleTime, PlayTransitions ? 20 : 2, 2, m_isClosing);\n        auto redStripHighlightMotionX1 = Lerp(res.x + redStripHighlightWidth, min.x + redStripHighlightMotionOffsetX, redStripHighlightMotionX1Time);\n        auto redStripHighlightMotionX2 = Lerp(res.x + redStripHighlightWidth, min.x + redStripHighlightMotionOffsetX, redStripHighlightMotionX2Time);\n        auto redStripHighlightMotionAlpha1 = Lerp(0.0, 100.0, redStripHighlightMotionAlphaIn1Time);\n        auto redStripHighlightMotionAlpha2 = Lerp(0.0, 100.0, redStripHighlightMotionAlphaIn2Time);\n\n        if (redStripHighlightMotionAlphaIn1Time >= 1.0)\n            redStripHighlightMotionAlpha1 = Lerp(100.0, 0.0, redStripHighlightMotionAlphaOut1Time);\n\n        if (redStripHighlightMotionAlphaIn2Time >= 1.0)\n            redStripHighlightMotionAlpha2 = Lerp(100.0, 0.0, redStripHighlightMotionAlphaOut2Time);\n\n        // Draw animated highlights for title animation.\n        drawRedStripHighlight({ redStripHighlightMotionX1, redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, redStripHighlightMotionAlpha1));\n        drawRedStripHighlight({ redStripHighlightMotionX2, redStripHighlightY }, IM_COL32(255, 172, 0, 0), IM_COL32(255, 172, 0, redStripHighlightMotionAlpha2));\n    }\n\n    auto titleText = Title.empty() ? \"DUMMY\" : Title.data();\n    auto titleFontSize = Scale(33, true);\n    auto titleSize = g_pFntNewRodin->CalcTextSizeA(titleFontSize, FLT_MAX, 0.0f, titleText);\n    auto titleOffsetX = redStripCornerMax.x - Scale(105, true);\n    auto titleOffsetY = redStripMotion + Scale(3.5, true);\n    auto titleMotionTime = ComputeLinearMotion(m_titleTime, PlayTransitions && !m_isClosing ? 10 : 0, 10, m_isClosing);\n    auto titleOffsetXMotion = Lerp(max.x + titleSize.x, titleOffsetX, titleMotionTime);\n\n    if (!ReduceDraw && !m_previousTitle.empty())\n    {\n        auto prevTitleAlphaMotionTime = ComputeLinearMotion(m_titleTime, PlayTransitions ? 10 : 0, 3, m_isClosing);\n\n        // Draw previous title fading out.\n        drawList->AddText(g_pFntNewRodin, titleFontSize, { titleOffsetX, titleOffsetY }, IM_COL32(255, 255, 255, Lerp(255, 0, prevTitleAlphaMotionTime)), m_previousTitle.data());\n    }\n\n    // Draw title.\n    drawList->AddText(g_pFntNewRodin, titleFontSize, { titleOffsetXMotion, titleOffsetY }, IM_COL32(255, 255, 255, 255 * titleMotionTime), titleText);\n\n    if (!ReduceDraw)\n    {\n        // Draw top left corner metal plate.\n        drawList->AddImage(g_upTexMainMenu1.get(), topPlateCornerMin, topPlateCornerMax, GET_UV_COORDS(topPlateCornerUVs));\n\n        // Draw top right stretched metal plate.\n        SetHorizontalGradient(topPlateStretchMin, topPlateStretchMax, IM_COL32_WHITE, IM_COL32(200, 200, 200, 255));\n        drawList->AddImage(g_upTexMainMenu1.get(), topPlateStretchMin, topPlateStretchMax, GET_UV_COORDS(topPlateRightStretchUVs));\n        ResetGradient();\n\n        // Draw top left stretched metal plate for ultrawide.\n        if (g_aspectRatio > WIDE_ASPECT_RATIO)\n            AddImageFlipped(g_upTexMainMenu1.get(), { 0.0f, topPlateCornerMin.y }, { topPlateCornerMin.x + Scale(2, true), topPlateCornerMax.y }, GET_UV_COORDS(topPlateLeftStretchUVs), IM_COL32_WHITE, true);\n\n        // Draw flipped metal plates for narrow aspect ratios.\n        if (g_aspectRatio < WIDE_ASPECT_RATIO)\n        {\n            ImVec2 topPlateCornerExtendMin = { topPlateCornerMin.x, topPlateCornerMin.y - topPlateHeight };\n            ImVec2 topPlateCornerExtendMax = { topPlateCornerMax.x, topPlateCornerMin.y + Scale(1, true) };\n            ImVec2 topPlateStretchExtendMin = { topPlateStretchMin.x, topPlateStretchMin.y - topPlateHeight };\n            ImVec2 topPlateStretchExtendMax = { topPlateStretchMax.x, topPlateStretchMin.y + Scale(1, true) };\n\n            AddImageFlipped(g_upTexMainMenu1.get(), topPlateCornerExtendMin, topPlateCornerExtendMax, GET_UV_COORDS(topPlateCornerUVs), IM_COL32_WHITE, false, true);\n            SetHorizontalGradient(topPlateStretchExtendMin, topPlateStretchExtendMax, IM_COL32_WHITE, IM_COL32(200, 200, 200, 255));\n            AddImageFlipped(g_upTexMainMenu1.get(), topPlateStretchExtendMin, topPlateStretchExtendMax, GET_UV_COORDS(topPlateRightStretchUVs), IM_COL32_WHITE, false, true);\n            ResetGradient();\n        }\n    }\n\n    if (ShowDescription)\n    {\n        auto textCoverCornerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 150, 150);\n        auto textCoverCornerExtendUVs = PIXELS_TO_UV_COORDS(1024, 1024, 801, 400, 125, 150);\n        auto textCoverCentreUVs = PIXELS_TO_UV_COORDS(1024, 1024, 950, 400, 50, 150);\n        auto textCoverCornerUVCompensation = Scale(2, true);\n        auto textCoverOffsetY = Scale(16.4, true);\n        auto textCoverWidth = Scale(149.5, true);\n        auto textCoverHeight = Scale(150, true);\n        auto textCoverMotion = Lerp(max.y + textCoverHeight, max.y - textCoverOffsetY - textCoverHeight, borderMotionTime);\n        auto textCoverColour = IM_COL32(0, 23, 57, 255);\n\n        ImVec2 textCoverCornerLeftMin = { min.x, textCoverMotion };\n        ImVec2 textCoverCornerLeftMax = { textCoverCornerLeftMin.x + textCoverWidth, textCoverCornerLeftMin.y + textCoverHeight };\n        ImVec2 textCoverCentreMin = { textCoverCornerLeftMax.x, textCoverCornerLeftMin.y };\n        ImVec2 textCoverCentreMax = { max.x - textCoverWidth, textCoverCornerLeftMax.y };\n        ImVec2 textCoverCornerRightMin = { max.x - textCoverWidth, textCoverCornerLeftMin.y };\n        ImVec2 textCoverCornerRightMax = { max.x, textCoverCornerLeftMax.y };\n\n        if (ReduceDraw)\n        {\n            auto horzMargin = Scale(128, true);\n\n            ImVec2 textCoverClipMin = { textCoverCornerLeftMin.x + horzMargin, textCoverCornerLeftMin.y + Scale(14, true) };\n            ImVec2 textCoverClipMax = { textCoverCornerRightMax.x - horzMargin, textCoverCornerRightMax.y - Scale(90, true) };\n\n            drawList->PushClipRect(textCoverClipMin, textCoverClipMax);\n        }\n\n        if (!Description.empty())\n        {\n            auto descFadeScale = Scale(20, true);\n            auto descFontSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true);\n            auto descSize = g_pFntRodin->CalcTextSizeA(descFontSize, FLT_MAX, 0.0f, Description.data());\n\n            ImVec2 descBoundsMin = { textCoverCentreMin.x - Scale(18, true), textCoverCentreMin.y + Scale(20, true) };\n            ImVec2 descBoundsMax = { textCoverCentreMax.x + Scale(18, true), textCoverCentreMax.y - Scale(90, true) };\n            auto descBoundsWidth = descBoundsMax.x - descBoundsMin.x;\n\n            m_descPos =\n            {\n                descBoundsMin.x + ((descBoundsMax.x - descBoundsMin.x) / 2) - (descSize.x / 2),\n                descBoundsMin.y + ((descBoundsMax.y - descBoundsMin.y) / 2) - (descSize.y / 2)\n            };\n\n            if (descSize.x > descBoundsWidth)\n            {\n                auto descScrollMax = descSize.x - (descBoundsWidth - descFadeScale * 2);\n                auto descScrollSpeed = Scale(150, true);\n                auto descScrollDelay = 1.2f;\n\n                if (descScrollMax > 0.0f)\n                {\n                    auto horz = -m_inputListener.RightStickX;\n\n                    if (fabs(horz) > 0.25f)\n                    {\n                        m_isDescManualScrolling = true;\n                        m_descScrollOffset += horz * descScrollSpeed * App::s_deltaTime;\n                    }\n                    else if (m_isDescManualScrolling && fabs(horz) <= 0.25f)\n                    {\n                        m_isDescScrolling = false;\n                        m_isDescManualScrolling = false;\n                        m_descScrollTimer = 0.0f;\n                        m_descScrollDirection = horz > 0.0f ? 1.0f : -1.0f;\n                    }\n\n                    if (!m_isDescManualScrolling)\n                    {\n                        if (!m_isDescScrolling)\n                        {\n                            m_descScrollTimer += App::s_deltaTime;\n\n                            if (m_descScrollTimer >= descScrollDelay)\n                                m_isDescScrolling = true;\n                        }\n\n                        if (m_isDescScrolling)\n                        {\n                            m_descScrollOffset += descScrollSpeed * m_descScrollDirection * App::s_deltaTime;\n\n                            if (m_descScrollOffset >= descScrollMax)\n                            {\n                                m_isDescScrolling = false;\n                                m_descScrollOffset = descScrollMax;\n                                m_descScrollTimer = 0.0f;\n                                m_descScrollDirection = -1.0f;\n                            }\n                            else if (m_descScrollOffset <= 0.0f)\n                            {\n                                m_isDescScrolling = false;\n                                m_descScrollOffset = 0;\n                                m_descScrollTimer = 0.0f;\n                                m_descScrollDirection = 1.0f;\n                            }\n                        }\n                    }\n\n                    m_descScrollOffset = std::clamp(m_descScrollOffset, 0.0f, descScrollMax);\n                }\n                else\n                {\n                    m_isDescScrolling = false;\n                    m_descScrollOffset = 0.0f;\n                    m_descScrollTimer = 0.0f;\n                    m_descScrollDirection = 1.0f;\n                }\n\n                m_descPos.x = (descBoundsMin.x + descFadeScale) - m_descScrollOffset;\n            }\n\n            auto descAlphaMotionTime = ComputeLinearMotion(m_descTime, PlayTransitions ? 10 : 0, 15, m_isClosing);\n\n            // Draw text cover backdrop.\n            drawList->AddRectFilled({ 0.0f, textCoverMotion }, { res.x, textCoverMotion + textCoverHeight }, IM_COL32(0, 0, 0, 65));\n\n            // Draw previous description fading out.\n            if (!m_isClosing && !m_previousDesc.empty())\n                drawList->AddText(g_pFntRodin, descFontSize, m_previousDescPos, IM_COL32(255, 255, 255, Lerp(255, 0, descAlphaMotionTime)), m_previousDesc.data());\n\n            // Draw description.\n            drawList->AddText(g_pFntRodin, descFontSize, m_descPos, IM_COL32(255, 255, 255, Lerp(0, 255, descAlphaMotionTime)), Description.data());\n\n            // Draw left text cover.\n            drawList->AddImage(g_upTexMainMenu1.get(), { 0.0f, textCoverCornerLeftMin.y }, { textCoverCornerLeftMin.x + textCoverCornerUVCompensation, textCoverCornerLeftMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour);\n            drawList->AddImage(g_upTexMainMenu1.get(), textCoverCornerLeftMin, textCoverCornerLeftMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour);\n\n            // Draw centre text cover.\n            drawList->AddImage(g_upTexMainMenu1.get(), textCoverCentreMin, textCoverCentreMax, GET_UV_COORDS(textCoverCentreUVs), textCoverColour);\n\n            // Draw right text cover.\n            AddImageFlipped(g_upTexMainMenu1.get(), { textCoverCornerRightMax.x - textCoverCornerUVCompensation, textCoverCornerRightMin.y }, { res.x, textCoverCornerRightMax.y }, GET_UV_COORDS(textCoverCornerExtendUVs), textCoverColour);\n            AddImageFlipped(g_upTexMainMenu1.get(), textCoverCornerRightMin, textCoverCornerRightMax, GET_UV_COORDS(textCoverCornerUVs), textCoverColour, true);\n        }\n        else\n        {\n            // Draw blank text cover.\n            drawList->AddRectFilled({ 0.0f, textCoverCornerLeftMin.y }, { res.x, textCoverCornerLeftMax.y }, textCoverColour);\n        }\n    }\n\n    if (ReduceDraw)\n    {\n        drawList->PopClipRect();\n    }\n    else\n    {\n        auto bottomPlateUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, -17, 700, 145);\n        auto bottomPlateStretchUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, -17, 128, 145);\n        auto bottomPlateOffsetX = Scale(-60, true);\n        auto bottomPlateOffsetY = Scale(12, true);\n        auto bottomPlateWidth = Scale(700, true);\n        auto bottomPlateHeight = Scale(145, true);\n        auto bottomPlateMotion = Lerp(max.y + bottomPlateHeight, (max.y + bottomPlateOffsetY) - bottomPlateHeight, borderMotionTime);\n\n        ImVec2 bottomPlateLeftMin = { min.x + bottomPlateOffsetX, bottomPlateMotion };\n        ImVec2 bottomPlateLeftMax = { bottomPlateLeftMin.x + bottomPlateWidth, bottomPlateMotion + bottomPlateHeight };\n        ImVec2 bottomPlateRightMin = { max.x - bottomPlateWidth - bottomPlateOffsetX, bottomPlateLeftMin.y };\n        ImVec2 bottomPlateRightMax = { bottomPlateRightMin.x + bottomPlateWidth, bottomPlateLeftMax.y };\n        \n        // Draw bottom left metal plate.\n        drawList->AddImage(g_upTexMainMenu1.get(), bottomPlateLeftMin, bottomPlateLeftMax, GET_UV_COORDS(bottomPlateUVs));\n\n        // Draw bottom right metal plate.\n        AddImageFlipped(g_upTexMainMenu1.get(), bottomPlateRightMin, bottomPlateRightMax, GET_UV_COORDS(bottomPlateUVs), IM_COL32_WHITE, true);\n\n        // Draw stretched metal plates for ultrawide.\n        if (g_aspectRatio > WIDE_ASPECT_RATIO)\n        {\n            // Draw bottom left stretched metal plate.\n            AddImageFlipped(g_upTexMainMenu1.get(), { 0.0f, bottomPlateLeftMin.y }, { bottomPlateLeftMin.x, bottomPlateLeftMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true);\n\n            // Draw bottom right stretched metal plate.\n            AddImageFlipped(g_upTexMainMenu1.get(), { bottomPlateRightMax.x, bottomPlateRightMin.y }, { res.x, bottomPlateRightMax.y }, GET_UV_COORDS(bottomPlateStretchUVs), IM_COL32_WHITE, true);\n        }\n\n        // Draw flipped metal plates for narrow aspect ratios.\n        if (g_aspectRatio < WIDE_ASPECT_RATIO)\n        {\n            ImVec2 bottomPlateLeftExtendMin = { bottomPlateLeftMin.x, bottomPlateLeftMax.y };\n            ImVec2 bottomPlateLeftExtendMax = { bottomPlateLeftMax.x, bottomPlateRightMax.y + bottomPlateHeight };\n            ImVec2 bottomPlateRightExtendMin = { bottomPlateRightMin.x, bottomPlateRightMax.y };\n            ImVec2 bottomPlateRightExtendMax = { bottomPlateRightMax.x, bottomPlateRightMax.y + bottomPlateHeight };\n\n            AddImageFlipped(g_upTexMainMenu1.get(), bottomPlateLeftExtendMin, bottomPlateLeftExtendMax, GET_UV_COORDS(bottomPlateUVs), IM_COL32_WHITE, false, true);\n            AddImageFlipped(g_upTexMainMenu1.get(), bottomPlateRightExtendMin, bottomPlateRightExtendMax, GET_UV_COORDS(bottomPlateUVs), IM_COL32_WHITE, true, true);\n        }\n    }\n\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n\n    if (ShowVersionString)\n    {\n        auto verAlphaMotionTime = PlayTransitions ? ComputeLinearMotion(m_time, 0, m_isClosing ? 3 : 10, m_isClosing) : 1.0;\n\n        DrawVersionString(IM_COL32(0, 0, 0, 70 * verAlphaMotionTime));\n    }\n\n    // Draw faded letterbox at narrow aspect ratios.\n    if (g_aspectRatio < NARROW_ASPECT_RATIO)\n    {\n        BlackBar::Show(true);\n        BlackBar::SetBorderMargin(Scale(BlackBar::ms_MenuBorderMargin, true));\n    }\n}\n\nvoid CommonMenu::Open()\n{\n    m_isClosing = false;\n    m_time = ImGui::GetTime();\n    m_titleTime = m_time;\n    m_descTime = m_time;\n}\n\ndouble CommonMenu::Close(bool isAnimated)\n{\n    if (!m_isClosing)\n    {\n        m_isClosing = true;\n        m_time = ImGui::GetTime();\n        m_titleTime = m_time;\n        m_descTime = m_time;\n    }\n\n    return isAnimated ? ComputeLinearMotion(m_time, 0, ANIMATION_DURATION) : 1.0;\n}\n\nbool CommonMenu::IsOpen() const\n{\n    return !m_isClosing && ComputeLinearMotion(m_time, 0, ANIMATION_DURATION) >= 1.0;\n}\n\nvoid CommonMenu::SetTitle(std::string title, bool isAnimated)\n{\n    if (Title == title)\n        return;\n\n    m_previousTitle = Title;\n    Title = title;\n\n    if (!isAnimated)\n        return;\n\n    m_titleTime = ImGui::GetTime();\n}\n\nvoid CommonMenu::SetDescription(std::string desc, bool isAnimated)\n{\n    if (Description == desc)\n        return;\n\n    m_previousDesc = Description;\n    m_previousDescPos = m_descPos;\n    Description = desc;\n\n    resetDescScroll();\n\n    if (!isAnimated)\n        return;\n\n    m_descTime = ImGui::GetTime();\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/common_menu.h",
    "content": "#pragma once\n\n#include <sdl_listener.h>\n\nclass CommonMenu\n{\n    std::string m_previousTitle{};\n    std::string m_previousDesc{};\n\n    ImVec2 m_descPos{};\n    ImVec2 m_previousDescPos{};\n\n    bool m_isClosing{};\n    double m_time{};\n    double m_titleTime{};\n    double m_descTime{};\n\n    bool m_isDescScrolling{};\n    bool m_isDescManualScrolling{};\n    float m_descScrollOffset{};\n    float m_descScrollTimer{};\n    float m_descScrollDirection{ 1.0f };\n\n    class CommonMenuInputListener : public SDLEventListener\n    {\n    public:\n        float RightStickX{};\n\n        bool OnSDLEvent(SDL_Event* event) override\n        {\n            if (event->type == SDL_CONTROLLERAXISMOTION && event->caxis.axis == SDL_CONTROLLER_AXIS_RIGHTX)\n                RightStickX = event->caxis.value / 32767.0f;\n\n            return false;\n        }\n    }\n    m_inputListener{};\n\n    void resetDescScroll()\n    {\n        m_isDescScrolling = false;\n        m_descScrollOffset = 0.0f;\n        m_descScrollTimer = 0.0f;\n        m_descScrollDirection = 1.0f;\n    }\n\npublic:\n    std::string Title{};\n    std::string Description{};\n    bool PlayTransitions{};\n    bool ShowDescription{ true };\n    bool ShowVersionString{ true };\n    bool ReduceDraw{};\n\n    CommonMenu() {}\n\n    CommonMenu(std::string title, std::string desc, bool playTransitions = false)\n        : Title(title), Description(desc), PlayTransitions(playTransitions) {}\n\n    void Draw();\n    void Open();\n    double Close(bool isAnimated = true);\n    bool IsOpen() const;\n    void SetTitle(std::string title, bool isAnimated = true);\n    void SetDescription(std::string desc, bool isAnimated = true);\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/fader.cpp",
    "content": "#include \"fader.h\"\n#include \"imgui_utils.h\"\n#include <user/config.h>\n\nstatic bool g_isFading;\nstatic bool g_isFadeIn;\n\nstatic float g_startTime;\n\nstatic float g_duration;\nstatic ImU32 g_colour = IM_COL32_BLACK;\nstatic std::function<void()> g_endCallback;\nstatic float g_endCallbackDelay;\n\nvoid Fader::Draw()\n{\n    if (!s_isVisible)\n        return;\n\n    auto time = (ImGui::GetTime() - g_startTime) / g_duration;\n    auto alpha = 1.0f;\n\n    if (time >= g_duration)\n    {\n        if (time >= g_duration + g_endCallbackDelay)\n        {\n            if (g_endCallback)\n            {\n                g_endCallback();\n                g_endCallback = nullptr;\n            }\n\n            g_isFading = false;\n        }\n    }\n    else\n    {\n        alpha = g_isFadeIn\n            ? Lerp(1, 0, time)\n            : Lerp(0, 1, time);\n    }\n\n    if (g_isFadeIn && !g_isFading)\n        return;\n\n    auto colour = IM_COL32(g_colour & 0xFF, (g_colour >> 8) & 0xFF, (g_colour >> 16) & 0xFF, 255 * alpha);\n\n    ImGui::GetBackgroundDrawList()->AddRectFilled({ 0, 0 }, ImGui::GetIO().DisplaySize, colour);\n}\n\nstatic void DoFade(bool isFadeIn, float duration, std::function<void()> endCallback, float endCallbackDelay)\n{\n    if (g_isFading)\n        return;\n\n    g_isFading = true;\n    g_isFadeIn = isFadeIn;\n    g_startTime = ImGui::GetTime();\n    g_duration = duration;\n    g_endCallback = endCallback;\n    g_endCallbackDelay = endCallbackDelay;\n\n    Fader::s_isVisible = true;\n}\n\nvoid Fader::SetFadeColour(ImU32 colour)\n{\n    g_colour = colour;\n}\n\nvoid Fader::FadeIn(float duration, std::function<void()> endCallback, float endCallbackDelay)\n{\n    DoFade(true, duration, endCallback, endCallbackDelay);\n}\n\nvoid Fader::FadeOut(float duration, std::function<void()> endCallback, float endCallbackDelay)\n{\n    DoFade(false, duration, endCallback, endCallbackDelay);\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/fader.h",
    "content": "#pragma once\n\nclass Fader\n{\npublic:\n    static inline bool s_isVisible = false;\n\n    static void Draw();\n    static void SetFadeColour(ImU32 colour);\n    static void FadeIn(float duration = 1, std::function<void()> endCallback = nullptr, float endCallbackDelay = 0.75f);\n    static void FadeOut(float duration = 1, std::function<void()> endCallback = nullptr, float endCallbackDelay = 0.75f);\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/game_window.cpp",
    "content": "#include \"game_window.h\"\n#include <gpu/video.h>\n#include <os/logger.h>\n#include <os/user.h>\n#include <os/version.h>\n#include <app.h>\n#include <sdl_listener.h>\n#include <SDL_syswm.h>\n\n#if _WIN32\n#include <dwmapi.h>\n#include <shellscalingapi.h>\n#endif\n\n#include <res/images/game_icon.bmp.h>\n\nbool m_isFullscreenKeyReleased = true;\nbool m_isResizing = false;\n\nint Window_OnSDLEvent(void*, SDL_Event* event)\n{\n    if (ImGui::GetIO().BackendPlatformUserData != nullptr)\n        ImGui_ImplSDL2_ProcessEvent(event);\n\n    for (auto listener : GetEventListeners())\n    {\n        if (listener->OnSDLEvent(event))\n        {\n            return 0;\n        }\n    }\n\n    switch (event->type)\n    {\n        case SDL_QUIT:\n        {\n            if (App::s_isSaving)\n                break;\n\n            App::Exit();\n\n            break;\n        }\n\n        case SDL_KEYDOWN:\n        {\n            switch (event->key.keysym.sym)\n            {\n                // Toggle fullscreen on ALT+ENTER.\n                case SDLK_RETURN:\n                {\n                    if (!(event->key.keysym.mod & KMOD_ALT) || !m_isFullscreenKeyReleased)\n                        break;\n\n                    Config::Fullscreen = GameWindow::SetFullscreen(!GameWindow::IsFullscreen());\n\n                    if (Config::Fullscreen)\n                    {\n                        Config::Monitor = GameWindow::GetDisplay();\n                    }\n                    else\n                    {\n                        Config::WindowState = GameWindow::SetMaximised(Config::WindowState == EWindowState::Maximised);\n                    }\n\n                    // Block holding ALT+ENTER spamming window changes.\n                    m_isFullscreenKeyReleased = false;\n\n                    break;\n                }\n\n                // Restore original window dimensions on F2.\n                case SDLK_F2:\n                    Config::Fullscreen = GameWindow::SetFullscreen(false);\n                    GameWindow::ResetDimensions();\n                    break;\n\n                // Recentre window on F3.\n                case SDLK_F3:\n                {\n                    if (GameWindow::IsFullscreen())\n                        break;\n\n                    GameWindow::SetDimensions(GameWindow::s_width, GameWindow::s_height);\n\n                    break;\n                }\n            }\n\n            break;\n        }\n\n        case SDL_KEYUP:\n        {\n            switch (event->key.keysym.sym)\n            {\n                // Allow user to input ALT+ENTER again.\n                case SDLK_RETURN:\n                    m_isFullscreenKeyReleased = true;\n                    break;\n            }\n        }\n\n        case SDL_WINDOWEVENT:\n        {\n            switch (event->window.event)\n            {\n                case SDL_WINDOWEVENT_FOCUS_LOST:\n                    GameWindow::s_isFocused = false;\n                    SDL_ShowCursor(SDL_ENABLE);\n                    break;\n\n                case SDL_WINDOWEVENT_FOCUS_GAINED:\n                {\n                    GameWindow::s_isFocused = true;\n\n                    if (GameWindow::IsFullscreen())\n                        SDL_ShowCursor(GameWindow::s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE);\n\n                    break;\n                }\n\n                case SDL_WINDOWEVENT_RESTORED:\n                    Config::WindowState = EWindowState::Normal;\n                    break;\n\n                case SDL_WINDOWEVENT_MAXIMIZED:\n                    Config::WindowState = EWindowState::Maximised;\n                    break;\n\n                case SDL_WINDOWEVENT_RESIZED:\n                    m_isResizing = true;\n                    Config::WindowSize = -1;\n                    GameWindow::s_width = event->window.data1;\n                    GameWindow::s_height = event->window.data2;\n                    GameWindow::SetTitle(fmt::format(\"{} - [{}x{}]\", GameWindow::GetTitle(), GameWindow::s_width, GameWindow::s_height).c_str());\n                    break;\n\n                case SDL_WINDOWEVENT_MOVED:\n                    GameWindow::s_x = event->window.data1;\n                    GameWindow::s_y = event->window.data2;\n                    break;\n            }\n\n            break;\n        }\n\n        case SDL_USER_PLAYER_CHAR:\n            GameWindow::s_playerCharacter = static_cast<EPlayerCharacter>(event->user.code);\n            GameWindow::SetIcon(GameWindow::s_playerCharacter);\n            break;\n    }\n\n    return 0;\n}\n\nvoid GameWindow::Init(const char* sdlVideoDriver)\n{\n#ifdef __linux__\n    SDL_SetHint(\"SDL_APP_ID\", \"io.github.sonicnext_dev.marathonrecomp\");\n#endif\n\n    if (SDL_VideoInit(sdlVideoDriver) != 0 && sdlVideoDriver)\n    {\n        LOGFN_ERROR(\"Failed to initialise the SDL video driver: \\\"{}\\\". Falling back to default.\", sdlVideoDriver);\n        SDL_VideoInit(nullptr);\n    }\n\n    auto videoDriverName = SDL_GetCurrentVideoDriver();\n\n    if (videoDriverName)\n        LOGFN(\"SDL video driver: \\\"{}\\\"\", videoDriverName);\n\n    SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE);\n    SDL_AddEventWatch(Window_OnSDLEvent, s_pWindow);\n\n#ifdef _WIN32\n    SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);\n#endif\n\n    s_x = Config::WindowX;\n    s_y = Config::WindowY;\n    s_width = Config::WindowWidth;\n    s_height = Config::WindowHeight;\n\n    if (s_x == -1 && s_y == -1)\n        s_x = s_y = SDL_WINDOWPOS_CENTERED;\n\n    if (!IsPositionValid())\n        GameWindow::ResetDimensions();\n\n    s_pWindow = SDL_CreateWindow(\"Marathon Recompiled\", s_x, s_y, s_width, s_height, GetWindowFlags());\n\n    if (IsFullscreen())\n        SDL_ShowCursor(SDL_DISABLE);\n\n    SetDisplay(Config::Monitor);\n    SetIcon();\n    SetTitle();\n\n    SDL_SetWindowMinimumSize(s_pWindow, MIN_WIDTH, MIN_HEIGHT);\n\n    SDL_SysWMinfo info;\n    SDL_VERSION(&info.version);\n    SDL_GetWindowWMInfo(s_pWindow, &info);\n\n#if defined(_WIN32)\n    s_renderWindow = info.info.win.window;\n\n    if (Config::DisableDWMRoundedCorners)\n    {\n        DWM_WINDOW_CORNER_PREFERENCE wcp = DWMWCP_DONOTROUND;\n        DwmSetWindowAttribute(s_renderWindow, DWMWA_WINDOW_CORNER_PREFERENCE, &wcp, sizeof(wcp));\n    }\n#elif defined(PLUME_SDL_VULKAN_ENABLED)\n    s_renderWindow = s_pWindow;\n#elif defined(__linux__)\n    s_renderWindow = { info.info.x11.display, info.info.x11.window };\n#elif defined(__APPLE__)\n    s_renderWindow.window = info.info.cocoa.window;\n    s_renderWindow.view = SDL_Metal_GetLayer(SDL_Metal_CreateView(s_pWindow));\n#else\n    static_assert(false, \"Unknown platform.\");\n#endif\n\n    SetTitleBarColour();\n\n    SDL_ShowWindow(s_pWindow);\n}\n\nvoid GameWindow::Update()\n{\n    if (!GameWindow::IsFullscreen() && !GameWindow::IsMaximised() && !s_isChangingDisplay)\n    {\n        Config::WindowX = GameWindow::s_x;\n        Config::WindowY = GameWindow::s_y;\n        Config::WindowWidth = GameWindow::s_width;\n        Config::WindowHeight = GameWindow::s_height;\n    }\n\n    if (m_isResizing)\n    {\n        SetTitle();\n        m_isResizing = false;\n    }\n\n    if (g_needsResize)\n        s_isChangingDisplay = false;\n}\n\nSDL_Surface* GameWindow::GetIconSurface(void* pIconBmp, size_t iconSize)\n{\n    auto rw = SDL_RWFromMem(pIconBmp, iconSize);\n    auto surface = SDL_LoadBMP_RW(rw, 1);\n\n    if (!surface)\n        LOGF_ERROR(\"Failed to load icon: {}\", SDL_GetError());\n\n    return surface;\n}\n\nvoid GameWindow::SetIcon(void* pIconBmp, size_t iconSize)\n{\n    if (auto icon = GetIconSurface(pIconBmp, iconSize))\n    {\n        SDL_SetWindowIcon(s_pWindow, icon);\n        SDL_FreeSurface(icon);\n    }\n}\n\nvoid GameWindow::SetIcon(EPlayerCharacter player)\n{\n    // TODO: Per-character icons\n    switch (player) {\n        case EPlayerCharacter::Sonic:\n            break;\n        case EPlayerCharacter::Shadow:\n            break;\n        case EPlayerCharacter::Silver:\n            break;\n        case EPlayerCharacter::Blaze:\n            break;\n        case EPlayerCharacter::Amy:\n            break;\n        case EPlayerCharacter::Tails:\n            break;\n        case EPlayerCharacter::Rouge:\n            break;\n        case EPlayerCharacter::Knuckles:\n            break;\n    }\n\n    SetIcon(g_game_icon, sizeof(g_game_icon));\n}\n\nconst char* GameWindow::GetTitle()\n{\n    if (Config::UseOfficialTitleOnTitleBar)\n    {\n        return \"SONIC THE HEDGEHOG\";\n    }\n\n    return \"Marathon Recompiled\";\n}\n\nvoid GameWindow::SetTitle(const char* title)\n{\n    SDL_SetWindowTitle(s_pWindow, title ? title : GetTitle());\n}\n\nvoid GameWindow::SetTitleBarColour()\n{\n#if _WIN32\n    if (os::user::IsDarkTheme())\n    {\n        auto version = os::version::GetOSVersion();\n\n        if (version.Major < 10 || version.Build <= 17763)\n            return;\n\n        auto flag = version.Build >= 18985\n            ? DWMWA_USE_IMMERSIVE_DARK_MODE\n            : 19; // DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1\n\n        const DWORD useImmersiveDarkMode = 1;\n        DwmSetWindowAttribute(s_renderWindow, flag, &useImmersiveDarkMode, sizeof(useImmersiveDarkMode));\n    }\n#endif\n}\n\nbool GameWindow::IsFullscreen()\n{\n    return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP;\n}\n\nbool GameWindow::SetFullscreen(bool isEnabled)\n{\n    if (isEnabled)\n    {\n        SDL_SetWindowFullscreen(s_pWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);\n        SDL_ShowCursor(s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE);\n    }\n    else\n    {\n        SDL_SetWindowFullscreen(s_pWindow, 0);\n        SDL_ShowCursor(SDL_ENABLE);\n\n        SetIcon(GameWindow::s_playerCharacter);\n        SetDimensions(Config::WindowWidth, Config::WindowHeight, Config::WindowX, Config::WindowY);\n    }\n\n    return isEnabled;\n}\n    \nvoid GameWindow::SetFullscreenCursorVisibility(bool isVisible)\n{\n    s_isFullscreenCursorVisible = isVisible;\n\n    if (IsFullscreen())\n    {\n        SDL_ShowCursor(s_isFullscreenCursorVisible ? SDL_ENABLE : SDL_DISABLE);\n    }\n    else\n    {\n        SDL_ShowCursor(SDL_ENABLE);\n    }\n}\n\nbool GameWindow::IsMaximised()\n{\n    return SDL_GetWindowFlags(s_pWindow) & SDL_WINDOW_MAXIMIZED;\n}\n\nEWindowState GameWindow::SetMaximised(bool isEnabled)\n{\n    if (isEnabled)\n    {\n        SDL_MaximizeWindow(s_pWindow);\n    }\n    else\n    {\n        SDL_RestoreWindow(s_pWindow);\n    }\n\n    return isEnabled\n        ? EWindowState::Maximised\n        : EWindowState::Normal;\n}\n\nSDL_Rect GameWindow::GetDimensions()\n{\n    SDL_Rect rect{};\n\n    SDL_GetWindowPosition(s_pWindow, &rect.x, &rect.y);\n    SDL_GetWindowSize(s_pWindow, &rect.w, &rect.h);\n\n    return rect;\n}\n\nvoid GameWindow::GetSizeInPixels(int *w, int *h)\n{\n    SDL_GetWindowSizeInPixels(s_pWindow, w, h);\n}\n\nvoid GameWindow::SetDimensions(int w, int h, int x, int y)\n{\n    s_width = w;\n    s_height = h;\n    s_x = x;\n    s_y = y;\n\n    SDL_SetWindowSize(s_pWindow, w, h);\n    SDL_ResizeEvent(s_pWindow, w, h);\n\n    SDL_SetWindowPosition(s_pWindow, x, y);\n    SDL_MoveEvent(s_pWindow, x, y);\n}\n\nvoid GameWindow::ResetDimensions()\n{\n    s_x = SDL_WINDOWPOS_CENTERED;\n    s_y = SDL_WINDOWPOS_CENTERED;\n    s_width = DEFAULT_WIDTH;\n    s_height = DEFAULT_HEIGHT;\n\n    Config::WindowX = s_x;\n    Config::WindowY = s_y;\n    Config::WindowWidth = s_width;\n    Config::WindowHeight = s_height;\n}\n\nuint32_t GameWindow::GetWindowFlags()\n{\n    uint32_t flags = SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI;\n\n    if (Config::WindowState == EWindowState::Maximised)\n        flags |= SDL_WINDOW_MAXIMIZED;\n\n    if (Config::Fullscreen)\n        flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;\n\n#ifdef PLUME_SDL_VULKAN_ENABLED\n    flags |= SDL_WINDOW_VULKAN;\n#endif\n\n    return flags;\n}\n\nint GameWindow::GetDisplayCount()\n{\n    auto result = SDL_GetNumVideoDisplays();\n\n    if (result < 0)\n    {\n        LOGF_ERROR(\"Failed to get display count: {}\", SDL_GetError());\n        return 1;\n    }\n\n    return result;\n}\n\nint GameWindow::GetDisplay()\n{\n    return SDL_GetWindowDisplayIndex(s_pWindow);\n}\n\nvoid GameWindow::SetDisplay(int displayIndex)\n{\n    if (!IsFullscreen())\n        return;\n\n    if (GetDisplay() == displayIndex)\n        return;\n\n    s_isChangingDisplay = true;\n\n    SDL_Rect bounds;\n\n    if (SDL_GetDisplayBounds(displayIndex, &bounds) == 0)\n    {\n        SetFullscreen(false);\n        SetDimensions(bounds.w, bounds.h, bounds.x, bounds.y);\n        SetFullscreen(true);\n    }\n    else\n    {\n        ResetDimensions();\n    }\n}\n\nstd::vector<SDL_DisplayMode> GameWindow::GetDisplayModes(bool ignoreInvalidModes, bool ignoreRefreshRates)\n{\n    auto result = std::vector<SDL_DisplayMode>();\n    auto uniqueResolutions = std::set<std::pair<int, int>>();\n    auto displayIndex = GetDisplay();\n    auto modeCount = SDL_GetNumDisplayModes(displayIndex);\n\n    if (modeCount <= 0)\n        return result;\n\n    for (int i = modeCount - 1; i >= 0; i--)\n    {\n        SDL_DisplayMode mode;\n\n        if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0)\n        {\n            if (ignoreInvalidModes)\n            {\n                if (mode.w < MIN_WIDTH || mode.h < MIN_HEIGHT)\n                    continue;\n\n                SDL_DisplayMode desktopMode;\n\n                if (SDL_GetDesktopDisplayMode(displayIndex, &desktopMode) == 0)\n                {\n                    if (mode.w >= desktopMode.w || mode.h >= desktopMode.h)\n                        continue;\n                }\n            }\n\n            if (ignoreRefreshRates)\n            {\n                auto res = std::make_pair(mode.w, mode.h);\n\n                if (uniqueResolutions.find(res) == uniqueResolutions.end())\n                {\n                    uniqueResolutions.insert(res);\n                    result.push_back(mode);\n                }\n            }\n            else\n            {\n                result.push_back(mode);\n            }\n        }\n    }\n\n    return result;\n}\n\nint GameWindow::FindNearestDisplayMode()\n{\n    auto result = -1;\n    auto displayModes = GetDisplayModes();\n    auto currentDiff = std::numeric_limits<int>::max();\n\n    for (int i = 0; i < displayModes.size(); i++)\n    {\n        auto& mode = displayModes[i];\n\n        auto widthDiff = abs(mode.w - s_width);\n        auto heightDiff = abs(mode.h - s_height);\n        auto totalDiff = widthDiff + heightDiff;\n\n        if (totalDiff < currentDiff)\n        {\n            currentDiff = totalDiff;\n            result = i;\n        }\n    }\n\n    return result;\n}\n\nbool GameWindow::IsPositionValid()\n{\n    auto displayCount = GetDisplayCount();\n\n    for (int i = 0; i < displayCount; i++)\n    {\n        SDL_Rect bounds;\n\n        if (SDL_GetDisplayBounds(i, &bounds) == 0)\n        {\n            auto x = s_x;\n            auto y = s_y;\n\n            // Window spans across the entire display in windowed mode, which is invalid.\n            if (!Config::Fullscreen && s_width == bounds.w && s_height == bounds.h)\n                return false;\n\n            if (x == SDL_WINDOWPOS_CENTERED_DISPLAY(i))\n                x = bounds.w / 2 - s_width / 2;\n\n            if (y == SDL_WINDOWPOS_CENTERED_DISPLAY(i))\n                y = bounds.h / 2 - s_height / 2;\n\n            if (x >= bounds.x && x < bounds.x + bounds.w &&\n                y >= bounds.y && y < bounds.y + bounds.h)\n            {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/game_window.h",
    "content": "#pragma once\n\n#include <plume_render_interface_types.h>\n#include <user/config.h>\n#include <sdl_events.h>\n\n#define DEFAULT_WIDTH 1280\n#define DEFAULT_HEIGHT 720\n#define MIN_WIDTH 640\n#define MIN_HEIGHT 480\n\nclass GameWindow\n{\npublic:\n    static inline SDL_Window* s_pWindow = nullptr;\n    static inline plume::RenderWindow s_renderWindow;\n\n    static inline int s_x;\n    static inline int s_y;\n    static inline int s_width = DEFAULT_WIDTH;\n    static inline int s_height = DEFAULT_HEIGHT;\n\n    static inline EPlayerCharacter s_playerCharacter;\n\n    static inline bool s_isFocused;\n    static inline bool s_isFullscreenCursorVisible;\n    static inline bool s_isChangingDisplay;\n\n    static SDL_Surface* GetIconSurface(void* pIconBmp, size_t iconSize);\n    static void SetIcon(void* pIconBmp, size_t iconSize);\n    static void SetIcon(EPlayerCharacter player = EPlayerCharacter::Sonic);\n    static const char* GetTitle();\n    static void SetTitle(const char* title = nullptr);\n    static void SetTitleBarColour();\n    static bool IsFullscreen();\n    static bool SetFullscreen(bool isEnabled);\n    static void SetFullscreenCursorVisibility(bool isVisible);\n    static bool IsMaximised();\n    static EWindowState SetMaximised(bool isEnabled);\n    static SDL_Rect GetDimensions();\n    static void GetSizeInPixels(int *w, int *h);\n    static void SetDimensions(int w, int h, int x = SDL_WINDOWPOS_CENTERED, int y = SDL_WINDOWPOS_CENTERED);\n    static void ResetDimensions();\n    static uint32_t GetWindowFlags();\n    static int GetDisplayCount();\n    static int GetDisplay();\n    static void SetDisplay(int displayIndex);\n    static std::vector<SDL_DisplayMode> GetDisplayModes(bool ignoreInvalidModes = true, bool ignoreRefreshRates = true);\n    static int FindNearestDisplayMode();\n    static bool IsPositionValid();\n    static void Init(const char* sdlVideoDriver = nullptr);\n    static void Update();\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/imgui_utils.cpp",
    "content": "#include \"imgui_utils.h\"\n#include <gpu/imgui/imgui_snapshot.h>\n#include <hid/hid.h>\n#include <patches/aspect_ratio_patches.h>\n#include <ui/black_bar.h>\n#include <app.h>\n#include <decompressor.h>\n#include <version.h>\n\n#include <res/images/common/button_window.dds.h>\n#include <res/images/common/controller.dds.h>\n#include <res/images/common/kbm.dds.h>\n#include <res/images/common/window.dds.h>\n#include <res/images/common/select_arrow.dds.h>\n#include <res/images/common/main_menu1.dds.h>\n#include <res/images/common/main_menu7.dds.h>\n#include <res/images/common/main_menu8.dds.h>\n#include <res/images/common/arrow.dds.h>\n\nImFont* g_pFntRodin;\nImFont* g_pFntNewRodin;\n\nfloat g_fntRodinSize{};\n\nstd::unique_ptr<GuestTexture> g_upTexButtonWindow;\nstd::unique_ptr<GuestTexture> g_upTexController;\nstd::unique_ptr<GuestTexture> g_upTexKbm;\nstd::unique_ptr<GuestTexture> g_upTexWindow;\nstd::unique_ptr<GuestTexture> g_upTexSelectArrow;\nstd::unique_ptr<GuestTexture> g_upTexMainMenu1;\nstd::unique_ptr<GuestTexture> g_upTexMainMenu7;\nstd::unique_ptr<GuestTexture> g_upTexMainMenu8;\nstd::unique_ptr<GuestTexture> g_upTexArrow;\n\nvoid InitImGuiUtils()\n{\n    g_pFntRodin = ImFontAtlasSnapshot::GetFont(\"FOT-RodinPro-DB.otf\");\n    g_pFntNewRodin = ImFontAtlasSnapshot::GetFont(\"FOT-NewRodinPro-UB.otf\");\n\n    g_upTexButtonWindow = LOAD_ZSTD_TEXTURE(g_button_window);\n    g_upTexController = LOAD_ZSTD_TEXTURE(g_controller);\n    g_upTexKbm = LOAD_ZSTD_TEXTURE(g_kbm);\n    g_upTexWindow = LOAD_ZSTD_TEXTURE(g_window);\n    g_upTexSelectArrow = LOAD_ZSTD_TEXTURE(g_select_arrow);\n    g_upTexMainMenu1 = LOAD_ZSTD_TEXTURE(g_main_menu1);\n    g_upTexMainMenu7 = LOAD_ZSTD_TEXTURE(g_main_menu7);\n    g_upTexMainMenu8 = LOAD_ZSTD_TEXTURE(g_main_menu8);\n    g_upTexArrow = LOAD_ZSTD_TEXTURE(g_arrow);\n}\n\nvoid UpdateImGuiUtils()\n{\n    g_fntRodinSize = Scale(Config::Language == ELanguage::Japanese ? 28 : 27, true);\n}\n\nvoid SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom)\n{\n    SetGradient(min, max, top, top, bottom, bottom);\n}\n\nvoid SetHorizontalGradient(const ImVec2& min, const ImVec2& max, ImU32 left, ImU32 right)\n{\n    SetGradient(min, max, left, right, right, left);\n}\n\nvoid SetVerticalGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom)\n{\n    SetGradient(min, max, top, top, bottom, bottom);\n}\n\nvoid SetGradient(const ImVec2& min, const ImVec2& max, ImU32 topLeft, ImU32 topRight, ImU32 bottomRight, ImU32 bottomLeft)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetGradient);\n    callbackData->setGradient.boundsMin[0] = min.x;\n    callbackData->setGradient.boundsMin[1] = min.y;\n    callbackData->setGradient.boundsMax[0] = max.x;\n    callbackData->setGradient.boundsMax[1] = max.y;\n    callbackData->setGradient.gradientTopLeft = topLeft;\n    callbackData->setGradient.gradientTopRight = topRight;\n    callbackData->setGradient.gradientBottomRight = bottomRight;\n    callbackData->setGradient.gradientBottomLeft = bottomLeft;\n}\n\nvoid ResetGradient()\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetGradient);\n    memset(&callbackData->setGradient, 0, sizeof(callbackData->setGradient));\n}\n\nvoid SetShaderModifier(uint32_t shaderModifier)\n{\n    if (shaderModifier == IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT && Config::DisableLowResolutionFontOnCustomUI)\n        shaderModifier = IMGUI_SHADER_MODIFIER_NONE;\n\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetShaderModifier);\n    callbackData->setShaderModifier.shaderModifier = shaderModifier;\n}\n\nvoid SetOrigin(ImVec2 origin)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetOrigin);\n    callbackData->setOrigin.origin[0] = origin.x;\n    callbackData->setOrigin.origin[1] = origin.y;\n}\n\nvoid SetScale(ImVec2 scale)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetScale);\n    callbackData->setScale.scale[0] = scale.x;\n    callbackData->setScale.scale[1] = scale.y;\n}\n\nvoid SetTextSkew(float yCenter, float skewScale)\n{\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_TEXT_SKEW);\n    SetOrigin({ 0.0f, yCenter });\n    SetScale({ skewScale, 1.0f });\n}\n\nvoid ResetTextSkew()\n{\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n    SetOrigin({ 0.0f, 0.0f });\n    SetScale({ 1.0f, 1.0f });\n}\n\nvoid SetHorizontalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScaleLeft, float fadeScaleRight)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetMarqueeFade);\n    callbackData->setMarqueeFade.boundsMin[0] = min.x;\n    callbackData->setMarqueeFade.boundsMin[1] = min.y;\n    callbackData->setMarqueeFade.boundsMax[0] = max.x;\n    callbackData->setMarqueeFade.boundsMax[1] = max.y;\n\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_HORIZONTAL_MARQUEE_FADE);\n    SetScale({ fadeScaleLeft, fadeScaleRight });\n}\n\nvoid SetHorizontalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScale)\n{\n    SetHorizontalMarqueeFade(min, max, fadeScale, fadeScale);\n}\n\nvoid SetVerticalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScaleTop, float fadeScaleBottom)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetMarqueeFade);\n    callbackData->setMarqueeFade.boundsMin[0] = min.x;\n    callbackData->setMarqueeFade.boundsMin[1] = min.y;\n    callbackData->setMarqueeFade.boundsMax[0] = max.x;\n    callbackData->setMarqueeFade.boundsMax[1] = max.y;\n\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_VERTICAL_MARQUEE_FADE);\n    SetScale({ fadeScaleTop, fadeScaleBottom });\n}\n\nvoid SetVerticalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScale)\n{\n    SetVerticalMarqueeFade(min, max, fadeScale, fadeScale);\n}\n\nvoid ResetMarqueeFade()\n{\n    ResetGradient();\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n    SetScale({ 1.0f, 1.0f });\n}\n\nvoid SetOutline(float outline)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetOutline);\n    callbackData->setOutline.outline = outline;\n}\n\nvoid ResetOutline()\n{\n    SetOutline(0.0f);\n}\n\nvoid SetProceduralOrigin(ImVec2 proceduralOrigin)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetProceduralOrigin);\n    callbackData->setProceduralOrigin.proceduralOrigin[0] = proceduralOrigin.x;\n    callbackData->setProceduralOrigin.proceduralOrigin[1] = proceduralOrigin.y;\n}\n\nvoid ResetProceduralOrigin()\n{\n    SetProceduralOrigin({ 0.0f, 0.0f });\n}\n\nvoid SetAdditive(bool enabled)\n{\n    auto callbackData = AddImGuiCallback(ImGuiCallback::SetAdditive);\n    callbackData->setAdditive.enabled = enabled;\n}\n\nvoid ResetAdditive()\n{\n    SetAdditive(false);\n}\n\nvoid AddImageFlipped(ImTextureID texture, const ImVec2& min, const ImVec2& max, const ImVec2& uvMin, const ImVec2& uvMax, ImU32 col, bool flipHorz, bool flipVert)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    ImVec2 uv0 = uvMin;\n    ImVec2 uv1 = { uvMax.x, uvMin.y };\n    ImVec2 uv2 = uvMax;\n    ImVec2 uv3 = { uvMin.x, uvMax.y };\n\n    if (flipHorz)\n    {\n        std::swap(uv0.x, uv1.x);\n        std::swap(uv2.x, uv3.x);\n    }\n\n    if (flipVert)\n    {\n        std::swap(uv0.y, uv3.y);\n        std::swap(uv1.y, uv2.y);\n    }\n\n    ImVec2 p0 = min;\n    ImVec2 p1 = { max.x, min.y };\n    ImVec2 p2 = max;\n    ImVec2 p3 = { min.x, max.y };\n\n    drawList->AddImageQuad(texture, p0, p1, p2, p3, uv0, uv1, uv2, uv3, col);\n}\n\nfloat Scale(float size, bool useGameplayScale)\n{\n    auto result = size * g_aspectRatioScale;\n\n    if (useGameplayScale)\n        result *= g_aspectRatioGameplayScale;\n\n    return result;\n}\n\ndouble ComputeLoopMotion(double time, double offset, double total)\n{\n    return std::clamp(fmod((ImGui::GetTime() - time - (offset / 60.0)) / (total / 60.0), 1.0 + (total / 60.0)), 0.0, 1.0) / 1.0;\n}\n\ndouble ComputeLinearMotion(double time, double offset, double total, bool reverse)\n{\n    auto result = std::clamp((ImGui::GetTime() - time - offset / 60.0) / total * 60.0, 0.0, 1.0);\n\n    return reverse ? 1.0 - result : result;\n}\n\ndouble ComputeMotion(double time, double offset, double total, bool reverse)\n{\n    return sqrt(ComputeLinearMotion(time, offset, total, reverse));\n}\n\nvoid DrawArrows(ImVec2 min, ImVec2 max, double& time)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    constexpr auto FADE_IN_DURATION = 5.0;\n    constexpr auto FADE_OUT_DURATION = 121.0;\n    constexpr auto ARROWS_OFFSET_FRAMES = 3.0;\n\n    auto arrowUVs = PIXELS_TO_UV_COORDS(512, 512, 0, 0, 400, 434);\n    auto centre = ImVec2(min.x + ((max.x - min.x) / 2), min.y + ((max.y - min.y) / 2));\n\n    auto leftArrowWidth = Scale(400, true);\n    auto leftArrowHeight = Scale(434, true);\n    auto leftArrowNextOffset = Scale(230, true);\n\n    auto rightArrowWidth = Scale(200, true);\n    auto rightArrowHeight = Scale(217, true);\n    auto rightArrowNextOffset = Scale(150, true);\n\n    const auto leftArrowCount = int((max.x + leftArrowWidth) / leftArrowNextOffset) + 1;\n    const auto rightArrowCount = int((max.x + rightArrowWidth) / rightArrowNextOffset) + 1;\n    const auto fadeOutStartTime = (ARROWS_OFFSET_FRAMES * rightArrowCount) + (FADE_IN_DURATION * rightArrowCount) + 30.0;\n\n    auto computeArrowLoopMotion = [&](double offset) -> double\n    {\n        auto motionTime = 0.0;\n        auto fadeOutStartMotionTime = ComputeMotion(time, 0, fadeOutStartTime);\n\n        if (fadeOutStartMotionTime < 1.0)\n        {\n            motionTime = ComputeMotion(time, offset, FADE_IN_DURATION);\n        }\n        else\n        {\n            motionTime = ComputeMotion(time, fadeOutStartTime, FADE_OUT_DURATION, true);\n\n            if (motionTime <= 0.0)\n                time = ImGui::GetTime();\n        }\n\n        return std::clamp(motionTime, 0.0, 1.0);\n    };\n\n    auto leftArrowsOffsetFrames = ARROWS_OFFSET_FRAMES * (rightArrowCount - 1);\n    auto animDuration = ((FADE_IN_DURATION + FADE_OUT_DURATION) + (leftArrowsOffsetFrames + (ARROWS_OFFSET_FRAMES * leftArrowCount))) + fadeOutStartTime;\n\n    auto leftArrowOffsetMotionTime = ComputeMotion(time, 0, animDuration);\n    auto leftArrowGlobalOffset = Scale(Lerp(318, 298, leftArrowOffsetMotionTime), true);\n\n    auto rightArrowOffsetMotionTime = ComputeMotion(time, 0, animDuration - 20.0);\n    auto rightArrowGlobalOffset = Scale(Lerp(73, 103, rightArrowOffsetMotionTime), true);\n\n    auto rightBaseY = centre.y - (rightArrowHeight / 2) - Scale(19, true);\n    auto rightEndY = rightBaseY + rightArrowHeight;\n    \n    for (int i = 0; i < rightArrowCount; i++)\n    {\n        auto baseX = max.x - (i * rightArrowNextOffset) - rightArrowWidth + rightArrowGlobalOffset;\n        auto endX = baseX + rightArrowWidth;\n        auto opacity = Lerp(5, 45, std::clamp(baseX / max.x, 0.0f, 1.0f)) * computeArrowLoopMotion(ARROWS_OFFSET_FRAMES * (rightArrowCount - i));\n\n        drawList->AddImage(g_upTexArrow.get(), { baseX, rightBaseY }, { endX, rightEndY }, GET_UV_COORDS(arrowUVs), IM_COL32(255, 255, 255, opacity));\n    }\n\n    auto leftBaseY = centre.y - (leftArrowHeight / 2) - Scale(20, true);\n    auto leftEndY = leftBaseY + leftArrowHeight;\n\n    for (int i = 0; i < leftArrowCount; i++)\n    {\n        auto baseX = min.x + (i * leftArrowNextOffset) - leftArrowWidth + leftArrowGlobalOffset;\n        auto endX = baseX + leftArrowWidth;\n        auto opacity = Lerp(45, 5, std::clamp(baseX / max.x, 0.0f, 1.0f)) * computeArrowLoopMotion(leftArrowsOffsetFrames + (ARROWS_OFFSET_FRAMES * (leftArrowCount - i)));\n\n        drawList->AddImage(g_upTexArrow.get(), { endX, leftBaseY }, { baseX, leftEndY }, GET_UV_COORDS(arrowUVs), IM_COL32(255, 255, 255, opacity));\n    }\n}\n\nvoid DrawArrowCursor(ImVec2 pos, double time, bool isIntroAnim, bool isBlinkingAnim, bool isReversed)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto cursorUVs = PIXELS_TO_UV_COORDS(50, 50, 0, 0, 27, 50);\n    auto cursorScaleX = Scale(14, true);\n\n    for (int i = 0; i < 3; i++)\n    {\n        auto cursorMotionTime = isIntroAnim ? ComputeLinearMotion(time, i, 5, isReversed) : 1.0;\n        auto cursorScaleYMotion = Lerp(Scale(37.5, true), Scale(24, true), cursorMotionTime);\n        auto cursorOffsetXMotion = Lerp(0, Scale(8.5, true), cursorMotionTime);\n        auto cursorOffsetYMotion = Lerp(pos.y - cursorScaleYMotion / 6, pos.y, cursorMotionTime);\n        auto cursorRightMotion = (cursorOffsetXMotion * 3) - (cursorOffsetXMotion * i);\n        auto cursorAlphaMotionTime = isIntroAnim ? ComputeLinearMotion(time, i, 2, isReversed) : 1.0;\n        auto cursorAlphaMotion = (int)Lerp(0, 255, cursorAlphaMotionTime);\n\n        if (isBlinkingAnim)\n        {\n            auto cursorAlphaMotionInTime = ComputeLoopMotion(time, 3.0 * i, 12.0);\n            auto cursorAlphaMotionOutTime = ComputeLoopMotion(time, 3.0 * (i + 1), 12.0);\n\n            // horrible\n            cursorAlphaMotionTime = cursorAlphaMotionInTime >= 1.0\n                ? cursorAlphaMotionOutTime >= 1.0\n                    ? cursorAlphaMotionInTime\n                    : cursorAlphaMotionOutTime\n                : cursorAlphaMotionInTime;\n\n            cursorAlphaMotion = (int)Lerp(50 * (i + 1), 255, cursorAlphaMotionTime);\n        }\n\n        ImVec2 cursorMin = { pos.x + cursorRightMotion, cursorOffsetYMotion };\n        ImVec2 cursorMax = { pos.x + cursorRightMotion + cursorScaleX, cursorMin.y + cursorScaleYMotion };\n\n        drawList->AddImage(g_upTexSelectArrow.get(), cursorMin, cursorMax, GET_UV_COORDS(cursorUVs), IM_COL32(255, 255, 255, cursorAlphaMotion));\n    }\n}\n\ndouble ImValueDebug(double& value, double increment)\n{\n#ifdef _WIN32\n    if (GetAsyncKeyState(VK_OEM_PLUS) & 1)\n        value += increment;\n    if (GetAsyncKeyState(VK_OEM_MINUS) & 1)\n        value -= increment;\n#endif\n\n    LOGF_UTILITY(\"{}\", value);\n\n    return value;\n}\n\nvoid DrawContainerBox(ImVec2 min, ImVec2 max, float alpha)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto commonWidth = Scale(50);\n    auto commonHeight = Scale(50);\n    auto bottomHeight = Scale(5);\n\n    auto tl = PIXELS_TO_UV_COORDS(1024, 1024, 1, 400, 50, 50);\n    auto tc = PIXELS_TO_UV_COORDS(1024, 1024, 51, 400, 50, 50);\n    auto cl = PIXELS_TO_UV_COORDS(1024, 1024, 1, 450, 50, 50);\n    auto cc = PIXELS_TO_UV_COORDS(1024, 1024, 51, 450, 50, 50);\n    auto bl = PIXELS_TO_UV_COORDS(1024, 1024, 1, 500, 50, 50);\n    auto bc = PIXELS_TO_UV_COORDS(1024, 1024, 51, 500, 50, 50);\n\n    auto colour = IM_COL32(255, 255, 255, 100 * alpha);\n\n    drawList->AddImage(g_upTexMainMenu1.get(), min, { min.x + commonWidth, min.y + commonHeight }, GET_UV_COORDS(tl), colour);\n    drawList->AddImage(g_upTexMainMenu1.get(), { min.x + commonWidth, min.y }, { max.x, min.y + commonHeight }, GET_UV_COORDS(tc), colour);\n    drawList->AddImage(g_upTexMainMenu1.get(), { min.x, min.y + commonHeight }, { min.x + commonWidth, max.y - commonHeight }, GET_UV_COORDS(cl), colour);\n    drawList->AddImage(g_upTexMainMenu1.get(), { min.x + commonWidth, min.y + commonHeight }, { max.x, max.y - commonHeight }, GET_UV_COORDS(cc), colour);\n    drawList->AddImage(g_upTexMainMenu1.get(), { min.x, max.y - commonHeight }, { min.x + commonWidth, max.y + bottomHeight }, GET_UV_COORDS(bl), colour);\n    drawList->AddImage(g_upTexMainMenu1.get(), { min.x + commonWidth, max.y - commonHeight }, { max.x, max.y + bottomHeight }, GET_UV_COORDS(bc), colour);\n}\n\nvoid DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    drawList->AddText(font, fontSize, pos, colour, text, nullptr);\n}\n\nvoid DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& position, const ImVec2& min, const ImVec2& max, ImU32 color, const char* text, double time, double delay, double speed)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto rectWidth = max.x - min.x;\n    auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, text);\n    auto textX = position.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth);\n\n    drawList->PushClipRect(min, max, true);\n\n    if (textX <= position.x)\n        DrawTextBasic(font, fontSize, { textX, position.y }, color, text);\n\n    if (textX + textSize.x < position.x)\n        DrawTextBasic(font, fontSize, { textX + textSize.x + rectWidth, position.y }, color, text);\n\n    drawList->PopClipRect();\n}\n\nvoid DrawTextWithMarqueeShadow(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 colour, const char* text, double time, double delay, double speed, float offset, float radius, ImU32 shadowColour)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto rectWidth = max.x - min.x;\n    auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, text);\n    auto textX = pos.x - fmodf(std::max(0.0, ImGui::GetTime() - (time + delay)) * speed, textSize.x + rectWidth);\n\n    drawList->PushClipRect(min, max, true);\n\n    if (textX <= pos.x)\n        DrawTextWithShadow(font, fontSize, { textX, pos.y }, colour, text, offset, radius, shadowColour);\n\n    if (textX + textSize.x < pos.x)\n        DrawTextWithShadow(font, fontSize, { textX + textSize.x + rectWidth, pos.y }, colour, text, offset, radius, shadowColour);\n\n    drawList->PopClipRect();\n}\n\nvoid DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, float outlineSize, ImU32 outlineColor, uint32_t shaderModifier)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    SetOutline(outlineSize);\n    drawList->AddText(font, fontSize, pos, outlineColor, text);\n    ResetOutline();\n\n    if (shaderModifier != IMGUI_SHADER_MODIFIER_NONE)\n        SetShaderModifier(shaderModifier);\n\n    drawList->AddText(font, fontSize, pos, color, text);\n\n    if (shaderModifier != IMGUI_SHADER_MODIFIER_NONE)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n}\n\nvoid DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text, float offset, float radius, ImU32 shadowColour)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    offset = Scale(offset);\n\n    SetOutline(radius);\n    drawList->AddText(font, fontSize, { pos.x + offset, pos.y + offset }, shadowColour, text);\n    ResetOutline();\n\n    drawList->AddText(font, fontSize, pos, colour, text, nullptr);\n}\n\nvoid DrawTextParagraph(const ImFont* font, float fontSize, float maxWidth, const ImVec2& pos, float lineMargin, const char* text, std::function<void(const char*, ImVec2)> drawMethod, bool isCentred)\n{\n    auto lines = Split(text, font, fontSize, maxWidth);\n    auto paragraphSize = MeasureCentredParagraph(font, fontSize, lineMargin, lines);\n    auto offsetY = 0.0f;\n\n    for (auto& line : lines)\n    {\n        auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, line.c_str());\n        auto textX = pos.x;\n        auto textY = pos.y + offsetY;\n\n        if (isCentred)\n        {\n            textX = line.starts_with(\"- \")\n                ? pos.x - paragraphSize.x / 2\n                : pos.x - textSize.x / 2;\n\n            textY = pos.y - paragraphSize.y / 2 + offsetY;\n        }\n\n        drawMethod(line.c_str(), { textX, textY });\n\n        textX += textSize.x;\n        offsetY += textSize.y + Scale(lineMargin);\n    }\n}\n\nfloat CalcWidestTextSize(const ImFont* font, float fontSize, std::span<std::string> strs)\n{\n    auto result = 0.0f;\n\n    for (auto& str : strs)\n        result = std::max(result, font->CalcTextSizeA(fontSize, FLT_MAX, 0, str.c_str()).x);\n\n    return result;\n}\n\nstd::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis, bool usePrefixEllipsis)\n{\n    const std::string ellipsis = \"...\";\n\n    if (input.length() > maxLength)\n    {\n        if (useEllipsis && maxLength > ellipsis.length())\n        {\n            if (usePrefixEllipsis)\n            {\n                return ellipsis + input.substr(0, maxLength - ellipsis.length());\n            }\n            else\n            {\n                return input.substr(0, maxLength - ellipsis.length()) + ellipsis;\n            }\n        }\n        else\n        {\n            return input.substr(0, maxLength);\n        }\n    }\n\n    return input;\n}\n\nstd::vector<std::string> Split(const char* strStart, const ImFont* font, float fontSize, float maxWidth)\n{\n    if (!strStart)\n        return {};\n\n    std::vector<std::string> result;\n    float textWidth = 0.0f;\n    float lineWidth = 0.0f;\n    const float scale = fontSize / font->FontSize;\n    const char *str = strStart;\n    const char *strEnd = strStart + strlen(strStart);\n    const char *lineStart = strStart;\n    const bool wordWrapEnabled = (maxWidth > 0.0f);\n    const char *wordWrapEOL = nullptr;\n\n    auto IsKanji = [](const char* str, const char* strEnd)\n    {\n        const char* tempStr = str;\n        unsigned int c = (unsigned int)*tempStr;\n        if (c < 0x80)\n            tempStr += 1;\n        else\n            tempStr += ImTextCharFromUtf8(&c, tempStr, strEnd);\n\n        // Basic CJK and CJK Extension A\n        return (c >= 0x4E00 && c <= 0x9FBF) || (c >= 0x3400 && c <= 0x4DBF);\n    };\n\n    while (*str != 0) \n    {\n        if (wordWrapEnabled)\n        {\n            if (wordWrapEOL == nullptr)\n            {\n                wordWrapEOL = CalcWordWrapPositionA(font, scale, str, strEnd, maxWidth - lineWidth);\n            }\n\n            if (str >= wordWrapEOL)\n            {\n                if (IsKanji(str, strEnd))\n                {\n                    // If the current character is Kanji, move back to prevent splitting Kanji\n                    while (str > lineStart && IsKanji(str - 3, strEnd))\n                    {\n                        str -= 3;\n                    }\n                }\n\n                if (textWidth < lineWidth)\n                    textWidth = lineWidth;\n\n                result.emplace_back(lineStart, str);\n                lineWidth = 0.0f;\n                wordWrapEOL = nullptr;\n\n                while (str < strEnd && ImCharIsBlankA(*str))\n                    str++;\n\n                if (*str == '\\n')\n                    str++;\n\n                if (strncmp(str, \"\\u200B\", 3) == 0)\n                {\n                    str += 3;\n                }\n\n                lineStart = str;\n                continue;\n            }\n        }\n\n        const char *prevStr = str;\n        unsigned int c = (unsigned int)*str;\n        if (c < 0x80)\n            str += 1;\n        else\n            str += ImTextCharFromUtf8(&c, str, strEnd);\n\n        if (c < 32)\n        {\n            if (c == '\\n')\n            {\n                result.emplace_back(lineStart, str - 1);\n                lineStart = str;\n                textWidth = ImMax(textWidth, lineWidth);\n                lineWidth = 0.0f;\n                continue;\n            }\n\n            if (c == '\\r')\n            {\n                lineStart = str;\n                continue;\n            }\n        }\n\n        const float charWidth = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale;\n        if (lineWidth + charWidth >= maxWidth)\n        {\n            str = prevStr;\n            break;\n        }\n\n        lineWidth += charWidth;\n    }\n\n    if (str != lineStart) \n    {\n        result.emplace_back(lineStart, str);\n    }\n\n    return result;\n}\n\nImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, const std::vector<std::string>& lines)\n{\n    auto x = 0.0f;\n    auto y = 0.0f;\n\n    for (size_t i = 0; i < lines.size(); i++)\n    {\n        auto textSize = font->CalcTextSizeA(fontSize, FLT_MAX, 0, lines[i].c_str());\n\n        x = std::max(x, textSize.x);\n        y += textSize.y + Scale(lineMargin);\n    }\n\n    return { x, y };\n}\n\nImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float maxWidth, float lineMargin, const char* text)\n{\n    return MeasureCentredParagraph(font, fontSize, lineMargin, Split(text, font, fontSize, maxWidth));\n}\n\nfloat Lerp(float a, float b, float t)\n{\n    return a + (b - a) * t;\n}\n\nfloat Cubic(float a, float b, float t)\n{\n    return a + (b - a) * (t * t * t);\n}\n\nfloat Hermite(float a, float b, float t)\n{\n    return a + (b - a) * (t * t * (3 - 2 * t));\n}\n\nImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t)\n{\n    return { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t };\n}\n\nImU32 ColourLerp(ImU32 c0, ImU32 c1, float t)\n{\n    auto a = ImGui::ColorConvertU32ToFloat4(c0);\n    auto b = ImGui::ColorConvertU32ToFloat4(c1);\n\n    ImVec4 result;\n    result.x = a.x + (b.x - a.x) * t;\n    result.y = a.y + (b.y - a.y) * t;\n    result.z = a.z + (b.z - a.z) * t;\n    result.w = a.w + (b.w - a.w) * t;\n\n    return ImGui::ColorConvertFloat4ToU32(result);\n}\n\nvoid DrawVersionString(const ImU32 colour)\n{\n    auto& res = ImGui::GetIO().DisplaySize;\n    auto  drawList = ImGui::GetBackgroundDrawList();\n\n    auto fontSize = Scale(12, true);\n    auto textSize = g_pFntNewRodin->CalcTextSizeA(fontSize, FLT_MAX, 0, g_versionString);\n\n    auto textMarginX = Scale(8, true);\n    auto textMarginY = Scale(5, true);\n    auto textY = res.y - textSize.y - textMarginY;\n\n    if (g_aspectRatio < NARROW_ASPECT_RATIO)\n        textY -= g_vertCentre;\n\n    // TODO: remove this line after v1 release.\n    drawList->AddText(g_pFntNewRodin, fontSize, { textMarginX, textY }, colour, \"WORK IN PROGRESS\");\n    drawList->AddText(g_pFntNewRodin, fontSize, { res.x - textSize.x - textMarginX, textY }, colour, g_versionString);\n}\n\nstatic void DrawWindowArrow(const ImVec2 pos, float scale, float rotation, uint32_t colour)\n{\n    auto arrowRadius = Scale(63.0f * scale, true);\n\n    std::array<ImVec2, 4> vertices =\n    {\n        pos,                                          // Top Left\n        { pos.x + arrowRadius, pos.y },               // Top Right\n        { pos.x + arrowRadius, pos.y + arrowRadius }, // Bottom Right\n        { pos.x, pos.y + arrowRadius }                // Bottom Left\n    };\n\n    // Adjust base rotation, since the texture\n    // points the arrow to the bottom left.\n    auto adjRotation = rotation + 90.0f;\n\n    auto radians = adjRotation * (IM_PI / 180.0f);\n    auto c = cosf(radians);\n    auto s = sinf(radians);\n\n    auto& pivot = vertices[3];\n\n    // Rotate around bottom left.\n    for (auto& v : vertices)\n    {\n        float dx = v.x - pivot.x;\n        float dy = v.y - pivot.y;\n\n        v.x = pivot.x + dx * c - dy * s;\n        v.y = pivot.y + dx * s + dy * c;\n    }\n\n    // Adjust height to pivot.\n    for (auto& v : vertices)\n        v.y -= arrowRadius;\n\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto arrowUVs = PIXELS_TO_UV_COORDS(128, 128, 65, 0, 63, 63);\n\n    auto& uvMin = std::get<0>(arrowUVs);\n    auto& uvMax = std::get<1>(arrowUVs);\n\n    drawList->AddImageQuad(g_upTexWindow.get(), vertices[0], vertices[1], vertices[2], vertices[3], uvMin, { uvMax.x, uvMin.y }, { uvMax.x, uvMax.y }, { uvMin.x, uvMax.y }, colour);\n}\n\ndouble DrawWindow(const ImVec2 min, const ImVec2 max, bool isAnimated, double time, bool isClosing)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto motionTime = 1.0;\n\n    auto _min = min;\n    auto _max = max;\n\n    if (isAnimated)\n    {\n        motionTime = ComputeLinearMotion(time, 0, 8, isClosing);\n\n        auto centre = ImVec2{ min.x + ((max.x - min.x) / 2), min.y + ((max.y - min.y) / 2) };\n\n        _min = Lerp(centre, min, motionTime);\n        _max = Lerp(centre, max, motionTime);\n    }\n\n    constexpr auto containerTopColour = IM_COL32(20, 56, 130, 200);\n    constexpr auto containerBottomColour = IM_COL32(8, 22, 51, 200);\n\n    drawList->AddRectFilledMultiColor(_min, _max, containerTopColour, containerTopColour, containerBottomColour, containerBottomColour);\n\n    auto lineHorzUVs = PIXELS_TO_UV_COORDS(128, 128, 2, 0, 60, 5);\n    auto lineVertUVs = PIXELS_TO_UV_COORDS(128, 128, 0, 66, 5, 60);\n\n    auto lineScale = Scale(1, true);\n    auto lineOffsetRight = Scale(3, true);\n\n    // Top\n    drawList->AddImage(g_upTexWindow.get(), _min, { _max.x, _min.y + lineScale }, GET_UV_COORDS(lineHorzUVs));\n\n    // Bottom\n    drawList->AddImage(g_upTexWindow.get(), { _min.x, _max.y - lineOffsetRight }, { _max.x, (_max.y - lineOffsetRight) + lineScale }, GET_UV_COORDS(lineHorzUVs));\n\n    // Left\n    drawList->AddImage(g_upTexWindow.get(), _min, { _min.x + lineScale, _max.y }, GET_UV_COORDS(lineVertUVs));\n\n    // Right\n    drawList->AddImage(g_upTexWindow.get(), { _max.x - lineOffsetRight, _min.y }, { (_max.x - lineOffsetRight) + lineScale, _max.y }, GET_UV_COORDS(lineVertUVs));\n\n    SetAdditive(true);\n\n    constexpr auto arrowPixelRadius = 63.0f;\n    constexpr auto arrowInnerScale = 0.16f;\n    constexpr auto arrowOuterScale = 0.225f;\n    constexpr auto arrowOuterColour = IM_COL32(255, 255, 255, 45);\n\n    auto arrowOuterOffset = Scale(arrowPixelRadius * arrowOuterScale, true) / 2;\n\n    // Top Left (Inner)\n    DrawWindowArrow(_min, arrowInnerScale, 0.0f, containerTopColour);\n\n    // Top Right (Inner)\n    DrawWindowArrow({ _max.x, _min.y }, arrowInnerScale, 90.0f, containerTopColour);\n\n    // Bottom Right (Inner)\n    DrawWindowArrow(_max, arrowInnerScale, 180.0f, containerTopColour);\n\n    // Bottom Left (Inner)\n    DrawWindowArrow({ _min.x, _max.y }, arrowInnerScale, 270.0f, containerTopColour);\n\n    // Top Left (Outer)\n    DrawWindowArrow({ _min.x - arrowOuterOffset, _min.y - arrowOuterOffset }, arrowOuterScale, 0.0f, arrowOuterColour);\n\n    // Top Right (Outer)\n    DrawWindowArrow({ _max.x + arrowOuterOffset, _min.y - arrowOuterOffset }, arrowOuterScale, 90.0f, arrowOuterColour);\n\n    // Bottom Right (Outer)\n    DrawWindowArrow({ _max.x + arrowOuterOffset, _max.y + arrowOuterOffset }, arrowOuterScale, 180.0f, arrowOuterColour);\n\n    // Bottom Left (Outer)\n    DrawWindowArrow({ _min.x - arrowOuterOffset, _max.y + arrowOuterOffset }, arrowOuterScale, 270.0f, arrowOuterColour);\n\n    ResetAdditive();\n\n    drawList->PushClipRect(_min, _max);\n\n    return motionTime;\n}\n\nvoid DrawScrollArrows(ImVec2 min, ImVec2 max, float scale, double& time, bool top, bool bottom)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto scrollArrowUVs = PIXELS_TO_UV_COORDS(1024, 1024, 500, 450, 50, 50);\n    auto scrollArrowOffsetX = Scale(64, true);\n    auto scrollArrowAlphaMotionInTime = ComputeLinearMotion(time, 0, 3);\n    auto scrollArrowAlphaMotionPauseTime = ComputeLinearMotion(time, 3, 11);\n    auto scrollArrowAlphaMotionOutTime = ComputeLinearMotion(time, 11, 4, true);\n    auto scrollArrowAlphaMotionLoopTime = ComputeLinearMotion(time, 15, 50);\n    auto scrollArrowAlphaMotion = 255;\n\n    if (scrollArrowAlphaMotionPauseTime >= 1.0)\n    {\n        // Fade out arrows.\n        scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionOutTime;\n\n        // Reset loop.\n        if (scrollArrowAlphaMotionLoopTime >= 1.0)\n            time = ImGui::GetTime();\n    }\n    else\n    {\n        // Fade in arrows.\n        scrollArrowAlphaMotion = 255 * scrollArrowAlphaMotionInTime;\n    }\n\n    auto scrollArrowColourMotion = IM_COL32(255, 255, 255, scrollArrowAlphaMotion);\n\n    ImVec2 scrollArrowTopMin = min;\n    ImVec2 scrollArrowTopMax = { scrollArrowTopMin.x + scale, scrollArrowTopMin.y + scale };\n    ImVec2 scrollArrowBottomMin = { scrollArrowTopMin.x, max.y - scale };\n    ImVec2 scrollArrowBottomMax = { scrollArrowTopMax.x, scrollArrowBottomMin.y + scale };\n\n    if (top)\n        AddImageFlipped(g_upTexMainMenu1.get(), scrollArrowTopMin, scrollArrowTopMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion, false, true);\n\n    if (bottom)\n        drawList->AddImage(g_upTexMainMenu1.get(), scrollArrowBottomMin, scrollArrowBottomMax, GET_UV_COORDS(scrollArrowUVs), scrollArrowColourMotion);\n}\n\n// Taken from ImGui because we need to modify to break for '\\u200B\\ too\n// Simple word-wrapping for English, not full-featured. Please submit failing cases!\n// This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end.\n// FIXME: Much possible improvements (don't cut things like \"word !\", \"word!!!\" but cut within \"word,,,,\", more sensible support for punctuations, support for Unicode punctuations, etc.)\nconst char* CalcWordWrapPositionA(const ImFont* font, float scale, const char* text, const char* text_end, float wrap_width)\n{\n    // For references, possible wrap point marked with ^\n    //  \"aaa bbb, ccc,ddd. eee   fff. ggg!\"\n    //      ^    ^    ^   ^   ^__    ^    ^\n\n    // List of hardcoded separators: .,;!?'\"\n\n    // Skip extra blanks after a line returns (that includes not counting them in width computation)\n    // e.g. \"Hello    world\" --> \"Hello\" \"World\"\n\n    // Cut words that cannot possibly fit within one line.\n    // e.g.: \"The tropical fish\" with ~5 characters worth of width --> \"The tr\" \"opical\" \"fish\"\n    float line_width = 0.0f;\n    float word_width = 0.0f;\n    float blank_width = 0.0f;\n    wrap_width /= scale; // We work with unscaled widths to avoid scaling every characters\n\n    const char* word_end = text;\n    const char* prev_word_end = NULL;\n    bool inside_word = true;\n\n    const char* s = text;\n    IM_ASSERT(text_end != NULL);\n    while (s < text_end)\n    {\n        unsigned int c = (unsigned int)*s;\n        const char* next_s;\n        if (c < 0x80)\n            next_s = s + 1;\n        else\n            next_s = s + ImTextCharFromUtf8(&c, s, text_end);\n\n        if (c < 32)\n        {\n            if (c == '\\n')\n            {\n                line_width = word_width = blank_width = 0.0f;\n                inside_word = true;\n                s = next_s;\n                continue;\n            }\n            if (c == '\\r')\n            {\n                s = next_s;\n                continue;\n            }\n        }\n\n        const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX);\n        if (ImCharIsBlankW(c) || c == 0x200B)\n        {\n            if (inside_word)\n            {\n                line_width += blank_width;\n                blank_width = 0.0f;\n                word_end = s;\n            }\n            blank_width += char_width;\n            inside_word = false;\n        }\n        else\n        {\n            word_width += char_width;\n            if (inside_word)\n            {\n                word_end = next_s;\n            }\n            else\n            {\n                prev_word_end = word_end;\n                line_width += word_width + blank_width;\n                word_width = blank_width = 0.0f;\n            }\n\n            // Allow wrapping after punctuation.\n            inside_word = (c != '.' && c != ',' && c != ';' && c != '!' && c != '?' && c != '\\\"');\n        }\n\n        // We ignore blank width at the end of the line (they can be skipped)\n        if (line_width + word_width > wrap_width)\n        {\n            // Words that cannot possibly fit within an entire line will be cut anywhere.\n            if (word_width < wrap_width)\n                s = prev_word_end ? prev_word_end : word_end;\n            break;\n        }\n\n        s = next_s;\n    }\n\n    // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.\n    // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol).\n    if (s == text && text < text_end)\n        return s + 1;\n\n    return s;\n}\n\nstatic std::vector<std::string> ParseInterpolatedString(const char* str, bool includeTokens = true)\n{\n    std::vector<std::string> result;\n\n    // Skip nullptr or strings that cannot possibly\n    // contain variables (e.g. \"${}\" bare minimum).\n    if (!str || strlen(str) <= 3)\n        return result;\n\n    auto start = str;\n    auto ptr = str;\n\n    while (*ptr)\n    {\n        if (ptr[0] == '$' && ptr[1] == '{')\n        {\n            // Add leading text.\n            if (ptr > start)\n                result.emplace_back(start, ptr - start);\n\n            auto tokenStart = ptr;\n\n            // Skip \"${\".\n            ptr += 2;\n\n            // Seek to end token.\n            while (*ptr && *ptr != '}')\n                ++ptr;\n\n            if (*ptr == '}')\n            {\n                auto curStrStart = tokenStart;\n                auto curStrEnd = ++ptr;\n\n                if (!includeTokens)\n                {\n                    curStrStart += 2;\n                    --curStrEnd;\n                }\n\n                // Add variable.\n                result.emplace_back(curStrStart, curStrEnd - curStrStart);\n                start = ptr;\n            }\n            else\n            {\n                break;\n            }\n        }\n        else\n        {\n            ++ptr;\n        }\n    }\n\n    // Add trailing text.\n    if (ptr > start)\n        result.emplace_back(start, ptr - start);\n\n    return result;\n}\n\nstatic bool IsInterpolatedString(std::string_view str)\n{\n    return str.length() >= 3 && str[0] == '$' && str[1] == '{' && str.back() == '}';\n}\n\nImVec2 MeasureInterpolatedText(const ImFont* pFont, float fontSize, const char* pText, ImGuiTextInterpData* pInterpData)\n{\n    ImVec2 result{};\n\n    auto parsed = ParseInterpolatedString(pText);\n\n    auto measureText = [&](float paddingX, std::string_view str)\n    {\n        auto textSize = pFont->CalcTextSizeA(fontSize, FLT_MAX, 0, str.data());\n\n        result.x += textSize.x + paddingX;\n        result.y = std::max(result.y, textSize.y);\n    };\n\n    for (size_t i = 0; i < parsed.size(); i++)\n    {\n        auto& str = parsed[i];\n        auto  paddingX = i == parsed.size() - 1 ? 0 : Scale(2, true);\n\n        if (IsInterpolatedString(str))\n        {\n            auto parsedSingleNoTokens = ParseInterpolatedString(str.data(), false);\n\n            if (!parsedSingleNoTokens.size())\n                continue;\n\n            auto variableMapSingle = MapTextVariables(parsedSingleNoTokens[0].data());\n\n            if (!variableMapSingle.size())\n                continue;\n\n            auto& variable = variableMapSingle[0];\n            auto& variableType = variable.first;\n            auto& variableValue = variable.second;\n\n            if (variableType == \"picture\")\n            {\n                auto& pictureName = variableValue;\n                auto  pictureNameHash = HashStr(pictureName);\n\n                if (!pInterpData || !pInterpData->Picture.pupTexture || !pInterpData->pPictureCrops || !pInterpData->pPictureCrops->contains(pictureNameHash))\n                {\n                    auto placeholderScale = Scale(28, true);\n\n                    result.x += placeholderScale;\n                    result.y = std::max(result.y, placeholderScale);\n\n                    continue;\n                }\n\n                auto pictureCrop = FindHash<ImGuiTextPictureCrop>(*pInterpData->pPictureCrops, pictureNameHash);\n                auto pictureWidth = Scale(pictureCrop.Width, true);\n                auto pictureHeight = Scale(pictureCrop.Height, true);\n\n                result.x += pictureWidth + paddingX;\n                result.y = std::max(result.y, pictureHeight);\n            }\n            else if (variableType == \"locale\")\n            {\n                auto& localeName = variableValue;\n\n                measureText(paddingX, Localise(localeName));\n            }\n        }\n        else\n        {\n            measureText(paddingX, str);\n        }\n    }\n\n    return result;\n}\n\nvoid DrawInterpolatedText(const ImFont* pFont, float fontSize, const ImVec2& pos, ImU32 colour, const char* pText, ImGuiTextInterpData* pInterpData)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto parsed = ParseInterpolatedString(pText);\n    auto advanceX = 0.0f;\n    auto marginY = Config::Language == ELanguage::Japanese ? 0 : Scale(1, true);\n\n    auto drawText = [&](const ImVec2& pos, float paddingX, std::string_view str)\n    {\n        auto textSize = pFont->CalcTextSizeA(fontSize, FLT_MAX, 0, str.data());\n\n        drawList->AddText(pFont, fontSize, pos, colour, str.data());\n\n        advanceX += textSize.x + paddingX;\n    };\n\n    for (size_t i = 0; i < parsed.size(); i++)\n    {\n        auto& str = parsed[i];\n        auto  curPos = ImVec2{ pos.x + advanceX, pos.y + marginY };\n        auto  paddingX = i == parsed.size() - 1 ? 0 : Scale(2, true);\n\n        if (IsInterpolatedString(str))\n        {\n            auto parsedSingleNoTokens = ParseInterpolatedString(str.data(), false);\n        \n            if (!parsedSingleNoTokens.size())\n                continue;\n\n            auto variableMapSingle = MapTextVariables(parsedSingleNoTokens[0].data());\n        \n            if (!variableMapSingle.size())\n                continue;\n        \n            auto& variable = variableMapSingle[0];\n            auto& variableType = variable.first;\n            auto& variableValue = variable.second;\n        \n            if (variableType == \"picture\")\n            {\n                auto& pictureName = variableValue;\n                auto  pictureNameHash = HashStr(pictureName);\n\n                if (!pInterpData || !pInterpData->Picture.pupTexture || !pInterpData->pPictureCrops || !pInterpData->pPictureCrops->contains(pictureNameHash))\n                {\n                    auto placeholderScale = Scale(28, true);\n\n                    ImVec2 placeholderMin = { pos.x + advanceX, pos.y };\n                    ImVec2 placeholderMax = { placeholderMin.x + placeholderScale, placeholderMin.y + placeholderScale };\n\n                    advanceX += placeholderScale;\n\n                    drawList->AddRectFilled(placeholderMin, placeholderMax, IM_COL32(255, 0, 0, 255));\n\n                    continue;\n                }\n\n                auto pictureCrop = FindHash<ImGuiTextPictureCrop>(*pInterpData->pPictureCrops, pictureNameHash);\n                auto pictureUVs = PIXELS_TO_UV_COORDS(pInterpData->Picture.Width, pInterpData->Picture.Height, pictureCrop.X, pictureCrop.Y, pictureCrop.Width, pictureCrop.Height);\n                auto pictureWidth = Scale(pictureCrop.Width, true);\n                auto pictureHeight = Scale(pictureCrop.Height, true);\n\n                ImVec2 pictureMin = { pos.x + advanceX, pos.y };\n                ImVec2 pictureMax = { pictureMin.x + pictureWidth, pictureMin.y + pictureHeight };\n\n                advanceX += pictureWidth + paddingX;\n\n                drawList->AddImage(pInterpData->Picture.pupTexture->get(), pictureMin, pictureMax, GET_UV_COORDS(pictureUVs), colour);\n            }\n            else if (variableType == \"locale\")\n            {\n                auto& localeName = variableValue;\n\n                drawText(curPos, paddingX, Localise(localeName));\n            }\n        }\n        else\n        {\n            drawText(curPos, paddingX, str);\n        }\n    }\n}\n\nImGuiTextInterpData GetHidInterpTextData()\n{\n    auto buttonTexture = &g_upTexController;\n    auto buttonTextureWidth = uint16_t(256);\n    auto buttonTextureHeight = uint16_t(128);\n\n    auto buttonCrops = Config::IsControllerIconsPS3() \n        ? &g_buttonCropsPS3\n        : &g_buttonCropsXenon;\n\n    if (!App::s_isInit)\n    {\n        if (hid::g_inputDevice == hid::EInputDevice::Keyboard ||\n            hid::g_inputDevice == hid::EInputDevice::Mouse)\n        {\n            buttonTexture = &g_upTexKbm;\n            buttonTextureWidth = uint16_t(84);\n            buttonTextureHeight = uint16_t(28);\n        }\n\n        if (hid::g_inputDevice == hid::EInputDevice::Keyboard)\n        {\n            buttonCrops = &g_buttonCropsKeyboard;\n        }\n        else if (hid::g_inputDevice == hid::EInputDevice::Mouse)\n        {\n            buttonCrops = &g_buttonCropsMouse;\n        }\n    }\n\n    return { { buttonTexture, buttonTextureWidth, buttonTextureHeight }, buttonCrops };\n}\n\nconst xxHashMap<ImGuiTextPictureCrop> g_buttonCropsXenon =\n{\n    { HashStr(\"button_a\"),     { 0, 0, 28, 28 } },\n    { HashStr(\"button_b\"),     { 28, 0, 28, 28 } },\n    { HashStr(\"button_x\"),     { 56, 0, 28, 28 } },\n    { HashStr(\"button_y\"),     { 84, 0, 28, 28 } },\n    { HashStr(\"button_lb\"),    { 112, 0, 53, 28 } },\n    { HashStr(\"button_rb\"),    { 168, 0, 53, 28 } },\n    { HashStr(\"button_lt\"),    { 56, 29, 55, 28 } },\n    { HashStr(\"button_rt\"),    { 0, 29, 55, 28 } },\n    { HashStr(\"button_start\"), { 112, 28, 28, 28 } },\n    { HashStr(\"button_back\"),  { 140, 28, 28, 28 } }\n};\n\nconst xxHashMap<ImGuiTextPictureCrop> g_buttonCropsPS3 =\n{\n    { HashStr(\"button_a\"),     { 0, 56, 28, 28 } },\n    { HashStr(\"button_b\"),     { 28, 56, 28, 28 } },\n    { HashStr(\"button_x\"),     { 56, 56, 28, 28 } },\n    { HashStr(\"button_y\"),     { 84, 56, 28, 28 } },\n    { HashStr(\"button_lb\"),    { 112, 56, 48, 28 } },\n    { HashStr(\"button_rb\"),    { 0, 84, 48, 28 } },\n    { HashStr(\"button_lt\"),    { 168, 56, 48, 28 } },\n    { HashStr(\"button_rt\"),    { 56, 84, 48, 28 } },\n    { HashStr(\"button_start\"), { 140, 84, 28, 28 } },\n    { HashStr(\"button_back\"),  { 112, 84, 28, 28 } }\n};\n\nconst xxHashMap<ImGuiTextPictureCrop> g_buttonCropsMouse =\n{\n    { HashStr(\"button_a\"), { 0, 0, 28, 28 } },\n    { HashStr(\"button_b\"), { 56, 0, 28, 28 } }\n};\n\nconst xxHashMap<ImGuiTextPictureCrop> g_buttonCropsKeyboard =\n{\n    { HashStr(\"button_a\"), { 28, 0, 28, 28 } },\n    { HashStr(\"button_b\"), { 56, 0, 28, 28 } }\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/imgui_utils.h",
    "content": "#pragma once\n\n#include <gpu/imgui/imgui_common.h>\n#include <gpu/video.h>\n#include <xxHashMap.h>\n\n#define PIXELS_TO_UV_COORDS(textureWidth, textureHeight, x, y, width, height) \\\n    std::make_tuple(ImVec2((float)x / (float)textureWidth, (float)y / (float)textureHeight), \\\n                    ImVec2(((float)x + (float)width) / (float)textureWidth, ((float)y + (float)height) / (float)textureHeight))\n\n#define GET_UV_COORDS(tuple) std::get<0>(tuple), std::get<1>(tuple)\n\n#define CENTRE_TEXT_HORZ(min, max, textSize) min.x + ((max.x - min.x) - textSize.x) / 2\n#define CENTRE_TEXT_VERT(min, max, textSize) min.y + ((max.y - min.y) - textSize.y) / 2\n\n#define BREATHE_MOTION(start, end, time, rate) Lerp(start, end, (sin((ImGui::GetTime() - time) * (2.0f * M_PI / rate)) + 1.0f) / 2.0f)\n\n#define IM_COL32_WHITE_TRANS IM_COL32(255, 255, 255, 0)\n\nextern ImFont* g_pFntRodin;\nextern ImFont* g_pFntNewRodin;\n\nextern float g_fntRodinSize;\n\nextern std::unique_ptr<GuestTexture> g_upTexButtonWindow;\nextern std::unique_ptr<GuestTexture> g_upTexController;\nextern std::unique_ptr<GuestTexture> g_upTexKbm;\nextern std::unique_ptr<GuestTexture> g_upTexWindow;\nextern std::unique_ptr<GuestTexture> g_upTexSelectArrow;\nextern std::unique_ptr<GuestTexture> g_upTexMainMenu1;\nextern std::unique_ptr<GuestTexture> g_upTexMainMenu7;\nextern std::unique_ptr<GuestTexture> g_upTexMainMenu8;\n\nstruct ImGuiTextPicture\n{\n    std::unique_ptr<GuestTexture>* pupTexture{};\n    uint16_t Width{};\n    uint16_t Height{};\n};\n\nstruct ImGuiTextPictureCrop\n{\n    uint16_t X{};\n    uint16_t Y{};\n    uint16_t Width{};\n    uint16_t Height{};\n};\n\nextern const xxHashMap<ImGuiTextPictureCrop> g_buttonCropsXenon;\nextern const xxHashMap<ImGuiTextPictureCrop> g_buttonCropsPS3;\nextern const xxHashMap<ImGuiTextPictureCrop> g_buttonCropsMouse;\nextern const xxHashMap<ImGuiTextPictureCrop> g_buttonCropsKeyboard;\n\nstruct ImGuiTextInterpData\n{\n    ImGuiTextPicture Picture{};\n    const xxHashMap<ImGuiTextPictureCrop>* pPictureCrops{};\n};\n\nvoid InitImGuiUtils();\nvoid UpdateImGuiUtils();\n\nvoid SetGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom);\nvoid SetHorizontalGradient(const ImVec2& min, const ImVec2& max, ImU32 left, ImU32 right);\nvoid SetVerticalGradient(const ImVec2& min, const ImVec2& max, ImU32 top, ImU32 bottom);\nvoid SetGradient(const ImVec2& min, const ImVec2& max, ImU32 topLeft, ImU32 topRight, ImU32 bottomRight, ImU32 bottomLeft);\nvoid ResetGradient();\nvoid SetShaderModifier(uint32_t shaderModifier);\nvoid SetOrigin(ImVec2 origin);\nvoid SetScale(ImVec2 scale);\nvoid SetTextSkew(float yCenter, float skewScale);\nvoid ResetTextSkew();\nvoid SetHorizontalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScaleLeft, float fadeScaleRight);\nvoid SetHorizontalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScale);\nvoid SetVerticalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScaleTop, float fadeScaleBottom);\nvoid SetVerticalMarqueeFade(ImVec2 min, ImVec2 max, float fadeScale);\nvoid ResetMarqueeFade();\nvoid SetOutline(float outline);\nvoid ResetOutline();\nvoid SetProceduralOrigin(ImVec2 proceduralOrigin);\nvoid ResetProceduralOrigin();\nvoid SetAdditive(bool enabled);\nvoid ResetAdditive();\nvoid AddImageFlipped(ImTextureID texture, const ImVec2& min, const ImVec2& max, const ImVec2& uvMin = { 0, 0 }, const ImVec2& uvMax = { 0, 0 }, ImU32 col = IM_COL32_WHITE, bool flipHorz = false, bool flipVert = false);\nfloat Scale(float size, bool useGameplayScale = false);\ndouble ComputeLoopMotion(double time, double offset, double total);\ndouble ComputeLinearMotion(double time, double offset, double total, bool reverse = false);\ndouble ComputeMotion(double time, double offset, double total, bool reverse = false);\nvoid DrawArrows(ImVec2 min, ImVec2 max, double& time);\nvoid DrawArrowCursor(ImVec2 pos, double time, bool isIntroAnim = true, bool isBlinkingAnim = true, bool isReversed = false);\ndouble ImValueDebug(double& value, double increment = 1.0);\nvoid DrawContainerBox(ImVec2 min, ImVec2 max, float alpha = 1);\nvoid DrawTextBasic(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text);\nvoid DrawTextWithMarquee(const ImFont* font, float fontSize, const ImVec2& position, const ImVec2& min, const ImVec2& max, ImU32 color, const char* text, double time, double delay, double speed);\nvoid DrawTextWithMarqueeShadow(const ImFont* font, float fontSize, const ImVec2& pos, const ImVec2& min, const ImVec2& max, ImU32 colour, const char* text, double time, double delay, double speed, float offset = 2.0f, float radius = 1.0f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255));\nvoid DrawTextWithOutline(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 color, const char* text, float outlineSize, ImU32 outlineColor, uint32_t shaderModifier = IMGUI_SHADER_MODIFIER_NONE);\nvoid DrawTextWithShadow(const ImFont* font, float fontSize, const ImVec2& pos, ImU32 colour, const char* text, float offset = 2.0f, float radius = 1.0f, ImU32 shadowColour = IM_COL32(0, 0, 0, 255));\nvoid DrawTextParagraph(const ImFont* font, float fontSize, float maxWidth, const ImVec2& pos, float lineMargin, const char* text, std::function<void(const char*, ImVec2)> drawMethod, bool isCentred = false);\nfloat CalcWidestTextSize(const ImFont* font, float fontSize, std::span<std::string> strs);\nstd::string Truncate(const std::string& input, size_t maxLength, bool useEllipsis = true, bool usePrefixEllipsis = false);\nstd::vector<std::string> Split(const char* strStart, const ImFont* font, float fontSize, float maxWidth);\nImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float lineMargin, const std::vector<std::string>& lines);\nImVec2 MeasureCentredParagraph(const ImFont* font, float fontSize, float maxWidth, float lineMargin, const char* text);\nfloat Lerp(float a, float b, float t);\nfloat Cubic(float a, float b, float t);\nfloat Hermite(float a, float b, float t);\nImVec2 Lerp(const ImVec2& a, const ImVec2& b, float t);\nImU32 ColourLerp(ImU32 c0, ImU32 c1, float t);\nvoid DrawVersionString(const ImU32 colour = IM_COL32(255, 255, 255, 70));\ndouble DrawWindow(const ImVec2 min, const ImVec2 max, bool isAnimated = false, double time = 0.0, bool isClosing = false);\nvoid DrawScrollArrows(ImVec2 min, ImVec2 max, float scale, double& time, bool top = true, bool bottom = true);\nconst char* CalcWordWrapPositionA(const ImFont* font, float scale, const char* text, const char* text_end, float wrap_width);\nImVec2 MeasureInterpolatedText(const ImFont* pFont, float fontSize, const char* pText, ImGuiTextInterpData* pInterpData = nullptr);\nvoid DrawInterpolatedText(const ImFont* pFont, float fontSize, const ImVec2& pos, ImU32 colour, const char* pText, ImGuiTextInterpData* pInterpData = nullptr);\nImGuiTextInterpData GetHidInterpTextData();\n"
  },
  {
    "path": "MarathonRecomp/ui/installer_wizard.cpp",
    "content": "#include \"installer_wizard.h\"\n\n#include <apu/embedded_player.h>\n#include <gpu/video.h>\n#include <gpu/imgui/imgui_snapshot.h>\n#include <hid/hid.h>\n#include <install/installer.h>\n#include <locale/locale.h>\n#include <patches/aspect_ratio_patches.h>\n#include <ui/button_window.h>\n#include <ui/common_menu.h>\n#include <ui/fader.h>\n#include <ui/game_window.h>\n#include <ui/imgui_utils.h>\n#include <ui/message_window.h>\n#include <decompressor.h>\n#include <exports.h>\n#include <nfd.h>\n#include <sdl_listener.h>\n\n#include <res/images/installer/install_001.dds.h>\n#include <res/images/installer/install_002.dds.h>\n#include <res/images/installer/install_003.dds.h>\n#include <res/images/installer/install_004.dds.h>\n#include <res/images/installer/install_005.dds.h>\n#include <res/images/installer/install_006.dds.h>\n#include <res/images/installer/install_007.dds.h>\n#include <res/images/installer/install_008.dds.h>\n#include <res/images/common/sonicnext-dev.dds.h>\n#include <res/credits.h>\n\nstatic constexpr float COMMON_MENU_INTRO_TIME = 10.0f;\nstatic constexpr float COMMON_FADE_TIME = 25.0f;\n\nstatic constexpr float NAV_BUTTON_OFFSET_Y = 39.0f;\nstatic constexpr float NAV_BUTTON_MARGIN = 48.0f;\n\nstatic CommonMenu g_commonMenu{};\n\nstatic std::array<std::unique_ptr<GuestTexture>, 8> g_installTextures{};\nstatic std::unique_ptr<GuestTexture> g_upTexSonicNextDev{};\n\nstatic double g_chevronTime{};\nstatic double g_cursorArrowsTime{};\nstatic double g_appearTime{};\n\nstatic double g_alphaTime{};\nstatic double g_alphaMotion{};\n\nstatic bool g_isIntroAnim{ true };\nstatic bool g_isDisappearing{};\nstatic bool g_isQuitting{};\n\nstatic std::filesystem::path g_installPath{};\nstatic std::filesystem::path g_gameSourcePath{};\nstatic std::array<std::filesystem::path, int(DLC::Count)> g_dlcSourcePaths{};\nstatic std::array<bool, int(DLC::Count)> g_dlcInstalled{};\n\nstatic Journal g_installerJournal{};\nstatic Installer::Sources g_installerSources{};\nstatic uint64_t g_installerAvailableSize{};\nstatic std::unique_ptr<std::thread> g_installerThread{};\nstatic double g_installerStartTime{};\nstatic double g_installerEndTime{ DBL_MAX };\nstatic float g_installerProgressRatioCurrent{};\nstatic std::atomic<float> g_installerProgressRatioTarget{};\nstatic std::atomic<bool> g_installerFinished{};\nstatic std::atomic<bool> g_installerHalted{};\nstatic std::atomic<bool> g_installerCancelled{};\nstatic bool g_installerFailed{};\nstatic std::string g_installerErrorMessage{};\n\nstatic std::array<ImVec2, 8> g_installTexturePositions =\n{\n    ImVec2(110.0f, 90.0f),  // Sonic\n    ImVec2(-20.0f, 90.0f),  // Shadow\n    ImVec2(110.0f, 10.0f),  // Silver\n    ImVec2(25.0f, 80.0f),   // Tails\n    ImVec2(100.0f, 90.0f),  // Rouge\n    ImVec2(110.0f, 90.0f),  // Amy\n    ImVec2(180.0f, 140.0f), // Elise\n    ImVec2(0.0f, 140.0f)    // Eggman\n};\n\nstatic const int g_installTextureIndices[] =\n{\n    0, // SelectLanguage\n    1, // Introduction\n    0, // SelectGame -- this page doesn't display a character.\n    0, // SelectDLC --- this page doesn't display a character.\n    2, // CheckSpace\n    3, // Installing\n    6, // InstallSucceeded -- force Elise.\n    7  // InstallFailed ----- force Eggman.\n};\n\nconst char* g_languageText[] =\n{\n    \"English\",  // English\n    \"日本語\",    // Japanese\n    \"Deutsch\",  // German\n    \"Français\", // French\n    \"Español\",  // Spanish\n    \"Italiano\"  // Italian\n};\n\nconst ELanguage g_languageEnum[] =\n{\n    ELanguage::English,\n    ELanguage::Japanese,\n    ELanguage::German,\n    ELanguage::French,\n    ELanguage::Spanish,\n    ELanguage::Italian\n};\n\nconst char* g_dlcText[] =\n{\n    \"Sonic Boss Attack\",\n    \"Shadow Boss Attack\",\n    \"Silver Boss Attack\",\n    \"Team Attack Amigo\",\n    \"Sonic/Very Hard\",\n    \"Shadow/Very Hard\",\n    \"Silver/Very Hard\",\n};\n\nenum class WizardPage\n{\n    SelectLanguage,\n    Introduction,\n    SelectGame,\n    SelectDLC,\n    CheckSpace,\n    Installing,\n    InstallSucceeded,\n    InstallFailed,\n};\n\nenum class MessagePromptSource\n{\n    Unknown,\n    Next,\n    Back\n};\n\nstatic WizardPage g_firstPage{};\nstatic WizardPage g_currentPage = g_firstPage;\n\nstatic std::string g_currentMessagePrompt{};\nstatic MessagePromptSource g_currentMessagePromptSource{};\nstatic bool g_currentMessagePromptConfirmation{};\nstatic int g_currentMessageResult{ -1 };\n\nstatic std::list<std::filesystem::path> g_currentPickerResults{};\nstatic std::atomic<bool> g_currentPickerResultsReady{};\nstatic std::string g_currentPickerErrorMessage{};\nstatic std::unique_ptr<std::thread> g_currentPickerThread{};\n\nstatic bool g_pickerTutorialCleared[2]{};\nstatic bool g_pickerTutorialTriggered{};\nstatic bool g_pickerTutorialFolderMode{};\nstatic bool g_currentPickerVisible{};\nstatic bool g_currentPickerFolderMode{};\n\nstatic int g_currentCursorIndex{ -1 };\nstatic int g_currentCursorDefault{};\nstatic bool g_currentCursorAccepted{};\nstatic bool g_currentCursorBack{};\nstatic std::vector<std::pair<ImVec2, ImVec2>> g_currentCursorRects{};\n\nstatic std::string g_creditsStr{};\n\nclass SDLEventListenerForInstaller : public SDLEventListener\n{\n    ImVec2 m_joypadAxis = {};\n\npublic:\n    bool OnSDLEvent(SDL_Event* event) override\n    {\n        if (!InstallerWizard::s_isVisible || g_isDisappearing)\n            return false;\n\n        auto noModals = g_currentMessagePrompt.empty() && !g_currentPickerVisible;\n\n        if (event->type == SDL_QUIT && g_currentPage == WizardPage::Installing)\n        {\n            // Pretend the back button was pressed if the user tried quitting during installation.\n            // This condition is above the rest of the event processing as we want to block the exit\n            // button while there's confirmation message is open as well.\n            if (noModals)\n                g_currentCursorBack = true;\n\n            return true;\n        }\n\n        if (!noModals || !hid::IsInputAllowed())\n            return false;\n\n        constexpr auto axisValueRange = 32767.0f;\n        constexpr auto axisTapRange = 0.5f;\n        auto newCursorIndex = -1;\n        auto tapDirection = ImVec2();\n\n        switch (event->type)\n        {\n            case SDL_KEYDOWN:\n            {\n                switch (event->key.keysym.scancode)\n                {\n                    case SDL_SCANCODE_LEFT:\n                    case SDL_SCANCODE_RIGHT:\n                        tapDirection.x = (event->key.keysym.scancode == SDL_SCANCODE_RIGHT) ? 1.0f : -1.0f;\n                        break;\n\n                    case SDL_SCANCODE_UP:\n                    case SDL_SCANCODE_DOWN:\n                        tapDirection.y = (event->key.keysym.scancode == SDL_SCANCODE_DOWN) ? 1.0f : -1.0f;\n                        break;\n\n                    case SDL_SCANCODE_RETURN:\n                    case SDL_SCANCODE_KP_ENTER:\n                        g_currentCursorAccepted = (g_currentCursorIndex >= 0);\n                        break;\n\n                    case SDL_SCANCODE_ESCAPE:\n                        g_currentCursorBack = true;\n                        break;\n                }\n\n                break;\n            }\n\n            case SDL_CONTROLLERBUTTONDOWN:\n            {\n                switch (event->cbutton.button)\n                {\n                    case SDL_CONTROLLER_BUTTON_DPAD_LEFT:\n                        tapDirection = { -1.0f, 0.0f };\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:\n                        tapDirection = { 1.0f, 0.0f };\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_DPAD_UP:\n                        tapDirection = { 0.0f, -1.0f };\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_DPAD_DOWN:\n                        tapDirection = { 0.0f, 1.0f };\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_A:\n                        g_currentCursorAccepted = (g_currentCursorIndex >= 0);\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_B:\n                        g_currentCursorBack = true;\n                        break;\n                }\n\n                break;\n            }\n\n            case SDL_CONTROLLERAXISMOTION:\n            {\n                if (event->caxis.axis < 2)\n                {\n                    auto newAxisValue = event->caxis.value / axisValueRange;\n                    auto sameDirection = (newAxisValue * m_joypadAxis[event->caxis.axis]) > 0.0f;\n                    auto wasInRange = abs(m_joypadAxis[event->caxis.axis]) > axisTapRange;\n                    auto isInRange = abs(newAxisValue) > axisTapRange;\n\n                    if (sameDirection && !wasInRange && isInRange)\n                        tapDirection[event->caxis.axis] = newAxisValue;\n\n                    m_joypadAxis[event->caxis.axis] = newAxisValue;\n                }\n\n                break;\n            }\n\n            case SDL_MOUSEBUTTONDOWN:\n            case SDL_MOUSEMOTION:\n            {\n                for (size_t i = 0; i < g_currentCursorRects.size(); i++)\n                {\n                    auto &currentRect = g_currentCursorRects[i];\n\n                    if (ImGui::IsMouseHoveringRect(currentRect.first, currentRect.second, false))\n                    {\n                        newCursorIndex = int(i);\n\n                        if (event->type == SDL_MOUSEBUTTONDOWN && event->button.button == SDL_BUTTON_LEFT)\n                            g_currentCursorAccepted = true;\n\n                        break;\n                    }\n                }\n\n                if (newCursorIndex < 0)\n                    g_currentCursorIndex = -1;\n\n                break;\n            }\n        }\n\n        if (tapDirection.x != 0.0f || tapDirection.y != 0.0f)\n        {\n            if (g_currentCursorIndex >= g_currentCursorRects.size() || g_currentCursorIndex < 0)\n            {\n                newCursorIndex = g_currentCursorDefault;\n            }\n            else\n            {\n                auto& currentRect = g_currentCursorRects[g_currentCursorIndex];\n\n                auto currentPoint = ImVec2\n                (\n                    (currentRect.first.x + currentRect.second.x) / 2.0f + tapDirection.x * (currentRect.second.x - currentRect.first.x) / 2.0f,\n                    (currentRect.first.y + currentRect.second.y) / 2.0f + tapDirection.y * (currentRect.second.y - currentRect.first.y) / 2.0f\n                );\n\n                auto closestDistance = FLT_MAX;\n\n                for (size_t i = 0; i < g_currentCursorRects.size(); i++)\n                {\n                    if (g_currentCursorIndex == i)\n                        continue;\n\n                    auto& targetRect = g_currentCursorRects[i];\n\n                    auto targetPoint = ImVec2\n                    (\n                        (targetRect.first.x + targetRect.second.x) / 2.0f + tapDirection.x * (targetRect.first.x - targetRect.second.x) / 2.0f,\n                        (targetRect.first.y + targetRect.second.y) / 2.0f + tapDirection.y * (targetRect.first.y - targetRect.second.y) / 2.0f\n                    );\n\n                    auto delta = ImVec2(targetPoint.x - currentPoint.x, targetPoint.y - currentPoint.y);\n                    auto projectedDistance = delta.x * tapDirection.x + delta.y * tapDirection.y;\n                    auto manhattanDistance = abs(delta.x) + abs(delta.y);\n\n                    if (projectedDistance > 0.0f && manhattanDistance < closestDistance)\n                    {\n                        newCursorIndex = int(i);\n                        closestDistance = manhattanDistance;\n                    }\n                }\n            }\n        }\n\n        if (newCursorIndex >= 0)\n        {\n            if (g_currentCursorIndex != newCursorIndex)\n            {\n                Game_PlaySound(\"move\");\n                g_cursorArrowsTime = ImGui::GetTime();\n            }\n\n            g_currentCursorIndex = newCursorIndex;\n        }\n\n        return false;\n    }\n}\ng_sdlEventListenerForInstaller;\n\nstatic void LeaveInstallerWizard(bool isQuitting = false)\n{\n    g_isDisappearing = true;\n\n    Fader::FadeOut\n    (\n        1.0f,\n\n        [=]()\n        {\n            g_isQuitting = isQuitting;\n            InstallerWizard::s_isVisible = false;\n        }\n    );\n}\n\nstatic int DLCIndex(DLC dlc)\n{\n    assert(dlc != DLC::Unknown);\n\n    return (int)(dlc) - 1;\n}\n\nstatic void SetCurrentPage(WizardPage page)\n{\n    g_currentPage = page;\n    g_cursorArrowsTime = ImGui::GetTime();\n\n    if (g_currentPage == WizardPage::InstallSucceeded)\n    {\n        ButtonWindow::Open(\"Button_Select\");\n    }\n    else if (g_currentPage != WizardPage::Installing)\n    {\n        auto key = \"Button_SelectBack\";\n\n        if (g_currentPage == g_firstPage || g_currentPage == WizardPage::InstallFailed)\n            key = \"Button_SelectQuit\";\n\n        ButtonWindow::Open(key);\n    }\n    else if (g_currentPage == WizardPage::Installing)\n    {\n        ButtonWindow::Open(\"Button_Cancel\");\n    }\n    else\n    {\n        ButtonWindow::Close();\n    }\n}\n\nstatic bool PushCursorRect(ImVec2 min, ImVec2 max, bool& isPressed, bool isDefault = false)\n{\n    auto currentIndex = int(g_currentCursorRects.size());\n\n    g_currentCursorRects.emplace_back(min, max);\n\n    if (isDefault)\n        g_currentCursorDefault = currentIndex;\n\n    if (g_currentCursorIndex == currentIndex)\n    {\n        if (g_currentCursorAccepted)\n        {\n            isPressed = true;\n            g_currentCursorAccepted = false;\n        }\n\n        return true;\n    }\n\n    return false;\n}\n\nstatic void DrawProgressBar(ImVec2 originMin, ImVec2 originMax, float progress)\n{\n    auto& res = ImGui::GetIO().DisplaySize;\n    auto  drawList = ImGui::GetBackgroundDrawList();\n\n    auto pos = ImVec2(originMin.x - Scale(8, true), originMax.y - Scale(NAV_BUTTON_OFFSET_Y, true));\n    auto edgeUVs = PIXELS_TO_UV_COORDS(256, 256, 1, 0, 51, 45);\n    auto stretchUVs = PIXELS_TO_UV_COORDS(256, 256, 51, 0, 51, 45);\n    auto edgeWidth = Scale(50, true);\n    auto edgeHeight = Scale(45, true);\n    auto colour = IM_COL32(255, 255, 255, 255 * g_alphaMotion);\n\n    auto leftEdgeMin = pos;\n    auto leftEdgeMax = ImVec2(leftEdgeMin.x + edgeWidth, leftEdgeMin.y + edgeHeight);\n    auto stretchMin = ImVec2(leftEdgeMax.x, leftEdgeMin.y);\n    auto stretchMax = ImVec2(stretchMin.x + Scale(400, true), leftEdgeMax.y);\n    auto rightEdgeMin = ImVec2(stretchMax.x, stretchMin.y);\n    auto rightEdgeMax = ImVec2(rightEdgeMin.x + edgeWidth, rightEdgeMin.y + edgeHeight);\n\n    drawList->AddImage(g_upTexMainMenu8.get(), leftEdgeMin, leftEdgeMax, GET_UV_COORDS(edgeUVs), colour);\n    drawList->AddImage(g_upTexMainMenu8.get(), stretchMin, stretchMax, GET_UV_COORDS(stretchUVs), colour);\n    AddImageFlipped(g_upTexMainMenu8.get(), rightEdgeMin, rightEdgeMax, GET_UV_COORDS(edgeUVs), colour, true);\n\n    auto gaugeOffsetX = Scale(41, true);\n    auto gaugeHeight = Scale(11, true);\n    auto gaugeMin = ImVec2(leftEdgeMin.x + gaugeOffsetX, (rightEdgeMin.y + edgeHeight / 2) - (gaugeHeight / 2) + Scale(1, true));\n    auto gaugeMax = ImVec2(rightEdgeMax.x - gaugeOffsetX, gaugeMin.y + gaugeHeight / 2);\n\n    drawList->AddRectFilled(gaugeMin, gaugeMax, IM_COL32(0, 0, 0, 255 * g_alphaMotion), Scale(10, true));\n    drawList->AddRectFilled(gaugeMin, { gaugeMin.x + (gaugeMax.x - gaugeMin.x) * progress, gaugeMax.y }, IM_COL32(112, 250, 255, 255 * g_alphaMotion), Scale(10, true));\n}\n\nstatic bool ConvertPathSet(const nfdpathset_t *pathSet, std::list<std::filesystem::path> &filePaths)\n{\n    nfdpathsetsize_t pathSetCount = 0;\n\n    if (NFD_PathSet_GetCount(pathSet, &pathSetCount) != NFD_OKAY)\n        return false;\n\n    for (nfdpathsetsize_t i = 0; i < pathSetCount; i++)\n    {\n        nfdnchar_t* pathSetPath = nullptr;\n\n        if (NFD_PathSet_GetPathN(pathSet, i, &pathSetPath) != NFD_OKAY)\n        {\n            filePaths.clear();\n            return false;\n        }\n\n        filePaths.emplace_back(std::filesystem::path(pathSetPath));\n\n        NFD_PathSet_FreePathN(pathSetPath);\n    }\n\n    return true;\n}\n\nstatic void PickerThreadProcess()\n{\n    auto result = NFD_ERROR;\n\n    const nfdpathset_t* pathSet;\n\n    if (g_currentPickerFolderMode)\n    {\n        result = NFD_PickFolderMultipleN(&pathSet, nullptr);\n    }\n    else\n    {\n        result = NFD_OpenDialogMultipleN(&pathSet, nullptr, 0, nullptr);\n    }\n    \n    if (result == NFD_OKAY)\n    {\n        auto pathsConverted = ConvertPathSet(pathSet, g_currentPickerResults);\n\n        NFD_PathSet_Free(pathSet);\n    }\n    else if (result == NFD_ERROR)\n    {\n        g_currentPickerErrorMessage = NFD_GetError();\n    }\n\n    g_currentPickerResultsReady = true;\n}\n\nstatic void PickerStart(bool folderMode)\n{\n    if (g_currentPickerThread != nullptr)\n    {\n        g_currentPickerThread->join();\n        g_currentPickerThread.reset();\n    }\n\n    g_currentPickerResults.clear();\n    g_currentPickerFolderMode = folderMode;\n    g_currentPickerResultsReady = false;\n    g_currentPickerVisible = true;\n\n    // Optional single thread mode for testing on systems\n    // that do not interact well with the separate thread\n    // being used for NFD.\n#ifdef __APPLE__\n    constexpr bool singleThreadMode = true;\n#else\n    constexpr bool singleThreadMode = false;\n#endif\n\n    if (singleThreadMode)\n        PickerThreadProcess();\n    else\n        g_currentPickerThread = std::make_unique<std::thread>(PickerThreadProcess);\n}\n\nstatic void PickerShow(bool folderMode)\n{\n    if (g_pickerTutorialCleared[folderMode])\n    {\n        PickerStart(folderMode);\n    }\n    else\n    {\n        g_currentMessagePrompt = Localise(folderMode ? \"Installer_Message_FolderPickerTutorial\" : \"Installer_Message_FilePickerTutorial\");\n        g_currentMessagePromptConfirmation = false;\n        g_pickerTutorialTriggered = true;\n        g_pickerTutorialFolderMode = folderMode;\n    }\n}\n\nstatic bool ParseSourcePaths(std::list<std::filesystem::path> &paths)\n{\n    assert((g_currentPage == WizardPage::SelectGame) || (g_currentPage == WizardPage::SelectDLC));\n\n    constexpr auto failedPathLimit = 5;\n    auto isFailedPathsOverLimit = false;\n\n    std::list<std::filesystem::path> failedPaths{};\n\n    if (g_currentPage == WizardPage::SelectGame)\n    {\n        for (const auto& path : paths)\n        {\n            if (Installer::parseGame(path))\n            {\n                g_gameSourcePath = path;\n            }\n            else if (failedPaths.size() < failedPathLimit)\n            {\n                failedPaths.push_back(path);\n            }\n            else\n            {\n                isFailedPathsOverLimit = true;\n            }\n        }\n    }\n    else if (g_currentPage == WizardPage::SelectDLC)\n    {\n        for (const auto& path : paths)\n        {\n            auto dlc = Installer::parseDLC(path);\n\n            if (dlc != DLC::Unknown)\n            {\n                g_dlcSourcePaths[DLCIndex(dlc)] = path;\n            }\n            else if (failedPaths.size() < failedPathLimit)\n            {\n                failedPaths.push_back(path);\n            }\n        }\n    }\n\n    if (!failedPaths.empty())\n    {\n        std::stringstream stringStream{};\n\n        stringStream << Localise(\"Installer_Message_InvalidFilesList\") << std::endl;\n\n        for (const auto& path : failedPaths)\n        {\n            std::u8string filenameU8 = path.filename().u8string();\n            stringStream << std::endl << \"- \" << Truncate(std::string(filenameU8.begin(), filenameU8.end()), 32, true, true);\n        }\n\n        if (isFailedPathsOverLimit)\n            stringStream << std::endl << \"- [...]\";\n\n        g_currentMessagePrompt = stringStream.str();\n        g_currentMessagePromptConfirmation = false;\n    }\n\n    return failedPaths.empty();\n}\n\nstatic void InstallerThread()\n{\n    auto result = Installer::install(g_installerSources, g_installPath, false, g_installerJournal, std::chrono::seconds(1),\n    //\n        [&]()\n        {\n            g_installerProgressRatioTarget = float(double(g_installerJournal.progressCounter) / double(g_installerJournal.progressTotal));\n\n            // If user is being asked for confirmation on cancelling\n            // the installation, halt the installer from progressing further.\n            g_installerHalted.wait(true);\n\n            // If user has confirmed they wish to cancel the\n            // installation, return false to indicate the installer\n            // should fail and stop.\n            return !g_installerCancelled.load();\n        }\n    );\n\n    if (!result)\n    {\n        g_installerFailed = true;\n        g_installerErrorMessage = g_installerJournal.lastErrorMessage;\n\n        // Delete all files that were copied.\n        Installer::rollback(g_installerJournal);\n    }\n\n    g_installerFinished = true;\n    g_installerCancelled = false;\n}\n\nstatic void InstallerStart()\n{\n    SetCurrentPage(WizardPage::Installing);\n\n    g_installerStartTime = ImGui::GetTime();\n    g_installerEndTime = DBL_MAX;\n    g_installerProgressRatioCurrent = 0.0f;\n    g_installerProgressRatioTarget = 0.0f;\n    g_installerFailed = false;\n    g_installerFinished = false;\n    g_installerThread = std::make_unique<std::thread>(InstallerThread);\n\n    g_commonMenu.SetTitle(Localise(\"Installer_Header_Installing\"));\n}\n\nstatic bool InstallerParseSources(std::string &errorMessage)\n{\n    std::error_code spaceInfoErrorCode;\n    auto spaceInfo = std::filesystem::space(g_installPath, spaceInfoErrorCode);\n\n    if (!spaceInfoErrorCode)\n        g_installerAvailableSize = spaceInfo.available;\n\n    Installer::Input installerInput{};\n    installerInput.gameSource = g_gameSourcePath;\n\n    for (auto& path : g_dlcSourcePaths)\n    {\n        if (path.empty())\n            continue;\n\n        installerInput.dlcSources.push_back(path);\n    }\n\n    auto sourcesParsed = Installer::parseSources(installerInput, g_installerJournal, g_installerSources);\n\n    errorMessage = g_installerJournal.lastErrorMessage;\n\n    return sourcesParsed;\n}\n\nstatic void CheckCancelAction()\n{\n    if (!g_currentCursorBack)\n        return;\n    \n    g_currentCursorBack = false;\n\n    if (g_currentPage == WizardPage::InstallSucceeded)\n    {\n        // Nothing to back out on this page.\n        return;\n    }\n    if (g_currentPage == WizardPage::Installing && g_installerCancelled)\n    {\n        // Installer's already been cancelled,\n        // no need for more confirmations.\n        return;\n    }\n\n    Game_PlaySound(\"window_close\");\n\n    if (g_currentPage == g_firstPage || g_currentPage == WizardPage::InstallFailed)\n    {\n        // Ask for confirmation if user wants to quit the installer.\n        g_currentMessagePrompt = Localise(\"Installer_Message_Quit\");\n        g_currentMessagePromptSource = MessagePromptSource::Back;\n        g_currentMessagePromptConfirmation = true;\n    }\n    else if (g_currentPage == WizardPage::Installing)\n    {\n        // Ask for confirmation if the user wants to cancel the installation.\n        g_currentMessagePrompt = Localise(\"Installer_Message_Cancel\");\n        g_currentMessagePromptSource = MessagePromptSource::Back;\n        g_currentMessagePromptConfirmation = true;\n\n        // Indicate to the installer that all progress should\n        // stop until the user confirms if they wish to cancel.\n        g_installerHalted = true;\n    }\n    else if (int(g_currentPage) > 0)\n    {\n        // Just go back to the previous page.\n        SetCurrentPage(WizardPage(int(g_currentPage) - 1));\n    }\n}\n\nstatic void DrawMessagePrompt()\n{\n    static auto s_messageWindowOpened = false;\n\n    if (g_currentMessagePrompt.empty())\n        return;\n\n    auto messageWindowReturned = false;\n\n    if (!s_messageWindowOpened)\n    {\n        // Update alpha time to fade out.\n        g_alphaTime = ImGui::GetTime();\n        s_messageWindowOpened = true;\n    }\n\n    if (g_currentMessagePromptConfirmation)\n    {\n        std::array<std::string, 2> buttons = { Localise(\"Common_Yes\"), Localise(\"Common_No\") };\n\n        messageWindowReturned = MessageWindow::Open(g_currentMessagePrompt, &g_currentMessageResult, buttons, 1);\n    }\n    else\n    {\n        messageWindowReturned = MessageWindow::Open(g_currentMessagePrompt, &g_currentMessageResult);\n    }\n\n    if (messageWindowReturned)\n    {\n        if (g_currentMessagePromptConfirmation && !g_currentMessageResult)\n        {\n            if (g_currentMessagePromptSource == MessagePromptSource::Back)\n            {\n                if (g_currentPage == WizardPage::Installing)\n                {\n                    // If user confirms they wish to cancel the\n                    // installation, notify the installer thread\n                    // it must finish as soon as possible.\n                    g_installerCancelled = true;\n                }\n                else\n                {\n                    // In all cases, proceed to just quit the application.\n                    LeaveInstallerWizard(true);\n                }\n            }\n            else if (g_currentPage == WizardPage::SelectDLC)\n            {\n                // If user confirms the message prompt that\n                // they wish to skip installing the DLC, proceed\n                // to the next step.\n                SetCurrentPage(WizardPage::CheckSpace);\n            }\n        }\n\n        if (g_currentMessagePromptSource == MessagePromptSource::Back)\n        {\n            // Regardless of the confirmation, the\n            // installation thread must be resumed.\n            g_installerHalted = false;\n            g_installerHalted.notify_all();\n        }\n\n        g_currentMessagePrompt.clear();\n        g_currentMessagePromptSource = MessagePromptSource::Unknown;\n        g_currentMessageResult = -1;\n\n        // Update alpha time to fade in.\n        g_alphaTime = ImGui::GetTime();\n\n        s_messageWindowOpened = false;\n    }\n}\n\nstatic void PickerDrawForeground()\n{\n    if (!g_currentPickerVisible)\n        return;\n\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    drawList->AddRectFilled({ 0.0f, 0.0f }, ImGui::GetIO().DisplaySize, IM_COL32(0, 0, 0, 190));\n}\n\nstatic void PickerCheckTutorial()\n{\n    if (!g_pickerTutorialTriggered || !g_currentMessagePrompt.empty())\n        return;\n\n    PickerStart(g_pickerTutorialFolderMode);\n\n    g_pickerTutorialTriggered = false;\n}\n\nstatic void PickerCheckResults()\n{\n    if (!g_currentPickerResultsReady)\n        return;\n\n    if (!g_currentPickerErrorMessage.empty())\n    {\n        g_currentMessagePrompt = g_currentPickerErrorMessage;\n        g_currentMessagePromptConfirmation = false;\n        g_currentPickerErrorMessage.clear();\n    }\n\n    if (!g_currentPickerResults.empty() && ParseSourcePaths(g_currentPickerResults))\n        g_pickerTutorialCleared[g_pickerTutorialFolderMode] = true;\n\n    g_currentPickerResultsReady = false;\n    g_currentPickerVisible = false;\n}\n\nstatic void DrawLeftImage()\n{\n    // Don't display character renders at game and DLC\n    // select pages, as we draw the sources on the left side.\n    if (g_currentPage == WizardPage::SelectGame || g_currentPage == WizardPage::SelectDLC)\n        return;\n\n    auto& res = ImGui::GetIO().DisplaySize;\n    auto  drawList = ImGui::GetBackgroundDrawList();\n    auto  installTextureIndex = g_installTextureIndices[int(g_currentPage)];\n\n    // Cycle through the available images while time passes during installation.\n    if (g_currentPage == WizardPage::Installing)\n    {\n        constexpr auto installSpeed = 1.0 / 15.0;\n        auto installTime = (ImGui::GetTime() - g_installerStartTime) * installSpeed;\n\n        installTextureIndex += int(installTime);\n    }\n\n    auto pTexture = g_installTextures[installTextureIndex % g_installTextures.size()].get();\n\n    constexpr float imageSize = 1000.0f;\n\n    auto pos = g_installTexturePositions[installTextureIndex % g_installTextures.size()];\n    auto min = ImVec2(g_aspectRatioOffsetX + Scale(pos.x, true), g_aspectRatioOffsetY + Scale(pos.y, true));\n    auto max = ImVec2(min.x + Scale(imageSize, true), min.y + Scale(imageSize, true));\n\n    drawList->AddImage(pTexture, min, max, { 0, 0 }, { 1, 1 }, IM_COL32(255, 255, 255, std::lround(140.0 * g_alphaMotion)));\n}\n\nstatic std::string& GetWizardText(WizardPage page)\n{\n    switch (page)\n    {\n        case WizardPage::SelectLanguage:   return Localise(\"Installer_Page_SelectLanguage\");\n        case WizardPage::Introduction:     return Localise(\"Installer_Page_Introduction\");\n        case WizardPage::SelectGame:       return Localise(\"Installer_Page_SelectGame\");\n        case WizardPage::SelectDLC:        return Localise(\"Installer_Page_SelectDLC\");\n        case WizardPage::CheckSpace:       return Localise(\"Installer_Page_CheckSpace\");\n        case WizardPage::Installing:       return Localise(\"Installer_Page_Installing\");\n        case WizardPage::InstallSucceeded: return Localise(\"Installer_Page_InstallSucceeded\");\n        case WizardPage::InstallFailed:    return Localise(\"Installer_Page_InstallFailed\");\n    }\n\n    return g_localeMissing;\n}\n\nstatic bool DrawButton(ImVec2 pos, const char* text, bool& isHovered, bool isEnabled, bool isDefault = false)\n{\n    auto& res = ImGui::GetIO().DisplaySize;\n    auto  drawList = ImGui::GetBackgroundDrawList();\n\n    auto edgeUVs = PIXELS_TO_UV_COORDS(256, 256, 1, 0, 51, 45);\n    auto stretchUVs = PIXELS_TO_UV_COORDS(256, 256, 51, 0, 51, 45);\n    auto width = Scale(50, true);\n    auto height = Scale(45, true);\n    auto colourMotion = isHovered && isEnabled ? 255 : 0;\n    auto colour = IM_COL32(colourMotion, colourMotion, colourMotion, 245 * g_alphaMotion);\n    auto fadeColour = IM_COL32(colourMotion, colourMotion, colourMotion, 45 * g_alphaMotion);\n\n    auto edgeMin = ImVec2(pos.x - Scale(8, true), pos.y);\n    auto edgeMax = ImVec2(edgeMin.x + width, edgeMin.y + height);\n    auto stretchMin = ImVec2(edgeMax.x, edgeMin.y);\n    auto stretchMax = ImVec2(res.x, edgeMax.y);\n    auto fadeMin = ImVec2(stretchMax.x - ((stretchMax.x - edgeMin.x) / 2), stretchMax.y);\n    auto fadeMax = stretchMax;\n\n    SetHorizontalGradient(fadeMin, fadeMax, colour, fadeColour);\n    drawList->AddImage(g_upTexMainMenu8.get(), edgeMin, edgeMax, GET_UV_COORDS(edgeUVs), colour);\n    drawList->AddImage(g_upTexMainMenu8.get(), stretchMin, stretchMax, GET_UV_COORDS(stretchUVs), colour);\n    ResetGradient();\n\n    auto textPos = ImVec2(edgeMin.x + Scale(51, true), edgeMin.y + Scale(8, true));\n\n    auto textColour = isEnabled\n        ? IM_COL32(255, 255, 255, 255 * g_alphaMotion)\n        : IM_COL32(137, 137, 137, 255 * g_alphaMotion);\n\n    drawList->AddText(g_pFntRodin, g_fntRodinSize, textPos, textColour, text);\n\n    if (isHovered && g_alphaMotion >= 1.0f)\n        DrawArrowCursor({ edgeMin.x + Scale(8, true), edgeMin.y + Scale(9, true) }, g_cursorArrowsTime, true, false);\n\n    auto isPressed = false;\n\n    isHovered = PushCursorRect(edgeMin, fadeMax, isPressed, isDefault);\n\n    if (isPressed)\n        Game_PlaySound(isEnabled ? \"main_deside\" : \"cannot_deside\");\n\n    return isEnabled && isPressed;\n}\n\nstatic void DrawSource(ImVec2 pos, const char* text, bool isEnabled)\n{\n    auto& res = ImGui::GetIO().DisplaySize;\n    auto  drawList = ImGui::GetBackgroundDrawList();\n\n    auto selectedUVs = PIXELS_TO_UV_COORDS(1024, 1024, 443, 524, 560, 47);\n    auto unselectedUVs = PIXELS_TO_UV_COORDS(1024, 1024, 443, 579, 560, 47);\n    auto categoryWidth = Scale(560, true);\n    auto categoryHeight = Scale(47, true);\n    auto colour = IM_COL32(255, 255, 255, 255 * g_alphaMotion);\n\n    ImVec2 categoryMin = { pos.x + Scale(42, true), pos.y + Scale(147, true) };\n    ImVec2 categoryMax = { categoryMin.x + categoryWidth, categoryMin.y + categoryHeight };\n\n    SetHorizontalGradient(categoryMin, categoryMax, colour, IM_COL32_WHITE_TRANS);\n    drawList->AddImage(g_upTexMainMenu7.get(), categoryMin, categoryMax, GET_UV_COORDS(isEnabled ? selectedUVs : unselectedUVs));\n    ResetGradient();\n\n    drawList->AddText(g_pFntRodin, g_fntRodinSize, { categoryMin.x + Scale(129, true), categoryMin.y + Scale(6, true) }, colour, text);\n\n    if (isEnabled)\n    {\n        auto cursorOffsetX = Scale(80, true);\n        auto cursorOffsetY = Scale(8, true);\n\n        DrawArrowCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }, 0, false, true);\n    }\n}\n\nstatic ImVec2 GetNavButtonPosition(ImVec2 originMin, ImVec2 originMax, int index)\n{\n    return { originMin.x, originMax.y - Scale(NAV_BUTTON_OFFSET_Y + (NAV_BUTTON_MARGIN * index), true) };\n}\n\nstatic void DrawSelectLanguagePage(ImVec2 originMin, ImVec2 originMax)\n{\n    auto languageBtnIdx = 5;\n\n    for (auto& language : g_languageEnum)\n    {\n        auto isHovered = Config::Language == language;\n        auto isPressed = DrawButton(GetNavButtonPosition(originMin, originMax, languageBtnIdx), g_languageText[int(language) - 1], isHovered, true);\n\n        if (isHovered)\n        {\n            Config::Language = language;\n            g_commonMenu.SetTitle(Localise(\"Installer_Header_Installer\"), false);\n        }\n\n        if (isPressed)\n            SetCurrentPage(WizardPage::Introduction);\n\n        languageBtnIdx--;\n    }\n}\n\nstatic void DrawSourcePickerPage(ImVec2 min, ImVec2 max, ImVec2 originMin, ImVec2 originMax)\n{\n    if (g_currentPage == WizardPage::SelectGame)\n    {\n        DrawSource(min, Localise(\"Installer_Step_Game\").c_str(), !g_gameSourcePath.empty());\n    }\n    else if (g_currentPage == WizardPage::SelectDLC)\n    {\n        auto offsetY = 0.0f;\n\n        for (int i = 0; i < 7; i++)\n        {\n            DrawSource({ min.x, min.y + offsetY }, g_dlcText[i], !g_dlcSourcePaths[i].empty() || g_dlcInstalled[i]);\n            offsetY += Scale(NAV_BUTTON_MARGIN, true);\n        }\n    }\n\n    static auto s_isAddFilesHovered = false;\n    static auto s_isAddFolderHovered = false;\n\n    auto isAddFilesPressed = DrawButton(GetNavButtonPosition(originMin, originMax, 2), Localise(\"Installer_Button_AddFiles\").c_str(), s_isAddFilesHovered, true);\n    auto isAddFolderPressed = DrawButton(GetNavButtonPosition(originMin, originMax, 1), Localise(\"Installer_Button_AddFolder\").c_str(), s_isAddFolderHovered, true);\n\n    if (isAddFilesPressed)\n        PickerShow(false);\n\n    if (isAddFolderPressed)\n        PickerShow(true);\n}\n\nstatic void DrawInstallingPage(ImVec2 originMin, ImVec2 originMax)\n{\n    constexpr auto progressSpeed = 0.1f;\n    auto ratioTarget = g_installerProgressRatioTarget.load();\n\n    g_installerProgressRatioCurrent += std::min(ratioTarget - g_installerProgressRatioCurrent, progressSpeed * ImGui::GetIO().DeltaTime);\n\n    DrawProgressBar(originMin, originMax, g_installerProgressRatioCurrent);\n\n    if (g_installerFinished)\n    {\n        g_installerThread->join();\n        g_installerThread.reset();\n\n        g_installerEndTime = ImGui::GetTime();\n\n        SetCurrentPage(g_installerFailed ? WizardPage::InstallFailed : WizardPage::InstallSucceeded);\n\n        g_commonMenu.SetTitle(Localise(\"Installer_Header_Installer\"));\n    }\n}\n\nstatic void DrawInstallSucceededPage(ImVec2 originMin, ImVec2 originMax, ImVec2 descriptionTextSize)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto marqueeTextSize = g_pFntRodin->CalcTextSizeA(g_fntRodinSize, FLT_MAX, 0, g_creditsStr.c_str());\n    auto marqueeTextMarginX = Scale(5, true);\n    auto marqueeTextMarginY = Scale(65, true);\n    auto marqueeTextPos = ImVec2(originMax.x, originMax.y - marqueeTextSize.y - marqueeTextMarginY);\n    auto marqueeTextMin = ImVec2(originMin.x, marqueeTextPos.y);\n    auto marqueeTextMax = ImVec2(originMax.x, originMax.y);\n\n    // NOTE (Hyper): shifting the first four rows of pixels out of view\n    // fixes a dark line appearing at the top of the logo, along with a\n    // couple chunks being taken away towards the upper right.\n    //\n    // This issue seems to occur a lot with various other textures,\n    // needing at least one or two pixels shifted right or down to\n    // prevent this error. Weird.\n    auto imageUVs = PIXELS_TO_UV_COORDS(5243, 450, 0, 4, 5243, 446);\n\n    auto imageWidth = Scale(524, true);\n    auto imageHeight = Scale(45, true);\n    auto imageRegionMin = ImVec2(originMin.x, originMin.y + descriptionTextSize.y);\n    auto imageRegionMax = ImVec2(originMax.x, originMax.y - (marqueeTextMax.y - marqueeTextMin.y));\n\n    auto imageMin = ImVec2\n    (\n        /* X */ imageRegionMin.x + ((imageRegionMax.x - imageRegionMin.x) / 2) - (imageWidth / 2),\n        /* Y */ imageRegionMin.y + ((imageRegionMax.y - imageRegionMin.y) / 2) - (imageHeight / 2)\n    );\n\n    auto imageMax = ImVec2(imageMin.x + imageWidth, imageMin.y + imageHeight);\n\n    drawList->AddImage(g_upTexSonicNextDev.get(), imageMin, imageMax, GET_UV_COORDS(imageUVs), IM_COL32_WHITE);\n\n    SetHorizontalMarqueeFade(marqueeTextMin, marqueeTextMax, Scale(32, true));\n    DrawTextWithMarquee(g_pFntRodin, g_fntRodinSize, marqueeTextPos, marqueeTextMin, marqueeTextMax, IM_COL32_WHITE, g_creditsStr.c_str(), g_installerEndTime, 0.8, Scale(250, true));\n    ResetMarqueeFade();\n}\n\nstatic void DrawMusicCredits()\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    constexpr auto fadeTime = COMMON_FADE_TIME;\n\n    // Wait 12 seconds before displaying music credits,\n    // as that's when the main melody begins.\n    constexpr auto fadeInOffset = 738;\n\n    // Wait 10 seconds after fade in before fading out music credits.\n    constexpr auto fadeOutOffset = fadeInOffset + fadeTime + 600;\n\n    auto fadeInMotion = ComputeLinearMotion(g_appearTime, fadeInOffset, fadeTime);\n    auto fadeOutMotion = ComputeLinearMotion(g_appearTime, fadeOutOffset, fadeTime);\n    auto fadeMotion = fadeInMotion - fadeOutMotion;\n    auto fadeAlphaMotion = IM_COL32(0, 0, 0, 70 * fadeMotion);\n\n    auto fontSize = Scale(12, true);\n    auto offsetX = Scale(8, true);\n    auto offsetY = Scale(5, true);\n\n    if (g_aspectRatio < NARROW_ASPECT_RATIO)\n        offsetY += g_vertCentre;\n\n    drawList->AddText(g_pFntNewRodin, fontSize, { offsetX, offsetY }, fadeAlphaMotion, Localise(\"Installer_MusicCredits\").c_str());\n}\n\nvoid InstallerWizard::Init()\n{\n    g_commonMenu = CommonMenu(Localise(\"Installer_Header_Installer\"), \"\", true);\n\n    g_installTextures[0] = LOAD_ZSTD_TEXTURE(g_install_001);\n    g_installTextures[1] = LOAD_ZSTD_TEXTURE(g_install_002);\n    g_installTextures[2] = LOAD_ZSTD_TEXTURE(g_install_003);\n    g_installTextures[3] = LOAD_ZSTD_TEXTURE(g_install_004);\n    g_installTextures[4] = LOAD_ZSTD_TEXTURE(g_install_005);\n    g_installTextures[5] = LOAD_ZSTD_TEXTURE(g_install_006);\n    g_installTextures[6] = LOAD_ZSTD_TEXTURE(g_install_007);\n    g_installTextures[7] = LOAD_ZSTD_TEXTURE(g_install_008);\n    g_upTexSonicNextDev  = LOAD_ZSTD_TEXTURE(g_sonicnextdev);\n\n    // Add whitespace between credits for marquee.\n    for (int i = 0; i < g_credits.size(); i++)\n    {\n        g_creditsStr += g_credits[i];\n        g_creditsStr += \"   \";\n    }\n}\n\nvoid InstallerWizard::Draw()\n{\n    if (!s_isVisible)\n        return;\n\n    g_alphaMotion = ComputeLinearMotion(g_alphaTime, g_isIntroAnim ? COMMON_MENU_INTRO_TIME : 0, COMMON_MENU_INTRO_TIME, !g_currentMessagePrompt.empty());\n\n    // Only offset fade motion for common menu intro.\n    if (g_alphaMotion >= 1.0)\n        g_isIntroAnim = false;\n\n    auto& res = ImGui::GetIO().DisplaySize;\n    auto  drawList = ImGui::GetBackgroundDrawList();\n\n    auto min = ImVec2(g_horzCentre + g_aspectRatioNarrowMargin, g_vertCentre);\n    auto max = ImVec2(res.x - min.x, res.y - min.y);\n    auto isMessageWindowOpen = !g_currentMessagePrompt.empty();\n\n    const auto bgGradientTop = IM_COL32(0, 103, 255, 255);\n    const auto bgGradientBottom = IM_COL32(0, 40, 100, 255);\n\n    drawList->AddRectFilledMultiColor({ 0.0, 0.0 }, res, bgGradientTop, bgGradientTop, bgGradientBottom, bgGradientBottom);\n\n    // Reset cursor rects.\n    g_currentCursorDefault = 0;\n    g_currentCursorRects.clear();\n\n    DrawArrows({ 0, 0 }, res, g_chevronTime);\n    DrawLeftImage();\n\n    g_commonMenu.Draw();\n\n    DrawMusicCredits();\n\n    auto containerWidth = Scale(640, true);\n    auto containerHeight = Scale(405, true);\n    auto containerOffsetY = Scale(135, true);\n    auto containerMin = ImVec2(max.x - containerWidth, min.y + containerOffsetY);\n    auto containerMax = ImVec2(res.x, containerMin.y + containerHeight);\n\n    DrawContainerBox(containerMin, containerMax, g_alphaMotion);\n\n    auto originMargin = Scale(38, true);\n    auto originMin = ImVec2(containerMin.x + originMargin, containerMin.y + originMargin);\n    auto originMax = ImVec2(max.x - originMargin, containerMax.y - originMargin);\n\n    auto text = GetWizardText(g_currentPage);\n    auto textMaxWidth = originMax.x - (g_fntRodinSize / 2.0f) - originMin.x;\n    auto textMargin = 5.0f;\n    auto textSize = MeasureCentredParagraph(g_pFntRodin, g_fntRodinSize, textMaxWidth, textMargin, text.c_str());\n\n    if (g_currentPage == WizardPage::Installing)\n    {\n        DrawInstallingPage(originMin, originMax);\n    }\n    else\n    {\n        auto isNavButtonSkip = false;\n        auto isNavButtonEnabled = true;\n        std::function<void()> navButtonFunction = []() { SetCurrentPage(WizardPage(int(g_currentPage) + 1)); };\n\n        switch (g_currentPage)\n        {\n            case WizardPage::SelectLanguage:\n                DrawSelectLanguagePage(originMin, originMax);\n                break;\n\n            case WizardPage::SelectGame:\n                DrawSourcePickerPage(min, max, originMin, originMax);\n                isNavButtonEnabled = !g_gameSourcePath.empty();\n                break;\n\n            case WizardPage::SelectDLC:\n            {\n                DrawSourcePickerPage(min, max, originMin, originMax);\n\n                isNavButtonSkip = std::all_of(g_dlcSourcePaths.begin(), g_dlcSourcePaths.end(), [](const std::filesystem::path& path) { return path.empty(); });\n\n                navButtonFunction = [&]()\n                {\n                    auto isDLCInstallerMode = g_gameSourcePath.empty();\n\n                    std::string sourcesErrorMessage{};\n\n                    if (!InstallerParseSources(sourcesErrorMessage))\n                    {\n                        // Some of the sources that were provided to the installer\n                        // are not valid. Restart the file selection process.\n                        std::stringstream stringStream{};\n\n                        stringStream << Localise(\"Installer_Message_InvalidFiles\");\n\n                        if (!sourcesErrorMessage.empty())\n                            stringStream << std::endl << std::endl << sourcesErrorMessage;\n\n                        g_currentMessagePrompt = stringStream.str();\n                        g_currentMessagePromptConfirmation = false;\n\n                        SetCurrentPage(isDLCInstallerMode ? WizardPage::SelectDLC : WizardPage::SelectGame);\n                    }\n                    else if (isNavButtonSkip && isDLCInstallerMode)\n                    {\n                        LeaveInstallerWizard();\n                    }\n                    else\n                    {\n                        SetCurrentPage(WizardPage::CheckSpace);\n                    }\n                };\n\n                break;\n            }\n\n            case WizardPage::CheckSpace:\n            {\n                char descriptionText[512]{};\n                char requiredSpaceText[128]{};\n                char availableSpaceText[128]{};\n\n                constexpr auto divisor = 1024.0 * 1024.0 * 1024.0;\n                auto requiredGiB = double(g_installerSources.totalSize) / divisor;\n                auto availableGiB = double(g_installerAvailableSize) / divisor;\n\n                snprintf(requiredSpaceText, sizeof(requiredSpaceText), Localise(\"Installer_Step_RequiredSpace\").c_str(), requiredGiB);\n                snprintf(availableSpaceText, sizeof(availableSpaceText), g_installerAvailableSize > 0 ? Localise(\"Installer_Step_AvailableSpace\").c_str() : \"\", availableGiB);\n                snprintf(descriptionText, sizeof(descriptionText), \"%s%s\\n%s\", text.c_str(), requiredSpaceText, availableSpaceText);\n\n                text = descriptionText;\n                navButtonFunction = []() { InstallerStart(); };\n\n                break;\n            }\n\n            case WizardPage::InstallSucceeded:\n                DrawInstallSucceededPage(originMin, originMax, textSize);\n                navButtonFunction = []() { LeaveInstallerWizard(); };\n                break;\n\n            case WizardPage::InstallFailed:\n                text += g_installerErrorMessage.c_str();\n                navButtonFunction = []() { SetCurrentPage(g_firstPage); };\n                break;\n        }\n\n        if (g_currentPage != WizardPage::SelectLanguage)\n        {\n            std::string navButtonText{};\n\n            if (isNavButtonSkip)\n                navButtonText = Localise(\"Installer_Button_Skip\");\n            else\n                navButtonText = Localise(\"Installer_Button_Next\");\n\n            static auto s_isNavButtonHovered = false;\n\n            if (DrawButton({ originMin.x, originMax.y - Scale(NAV_BUTTON_OFFSET_Y, true) }, navButtonText.c_str(), s_isNavButtonHovered, isNavButtonEnabled, true) && navButtonFunction)\n                navButtonFunction();\n        }\n    }\n\n    DrawTextParagraph(g_pFntRodin, g_fntRodinSize, textMaxWidth, originMin, textMargin, text.c_str(),\n        [&](const char* str, ImVec2 pos) { DrawTextBasic(g_pFntRodin, g_fntRodinSize, pos, IM_COL32(255, 255, 255, 255 * g_alphaMotion), str); });\n\n    CheckCancelAction();\n    DrawMessagePrompt();\n    PickerDrawForeground();\n    PickerCheckTutorial();\n    PickerCheckResults();\n}\n\nvoid InstallerWizard::Shutdown()\n{\n    // Wait for and reset the threads.\n    if (g_installerThread != nullptr)\n    {\n        g_installerThread->join();\n        g_installerThread.reset();\n    }\n\n    if (g_currentPickerThread != nullptr)\n    {\n        g_currentPickerThread->join();\n        g_currentPickerThread.reset();\n    }\n\n    // Free the sources.\n    g_installerSources.game.reset();\n    g_installerSources.dlc.clear();\n    \n    // Make sure the GPU is not currently active before deleting textures.\n    Video::WaitForGPU();\n\n    // Free the textures.\n    for (auto &texture : g_installTextures)\n        texture.reset();\n\n    g_upTexSonicNextDev.reset();\n}\n\nbool InstallerWizard::Run(std::filesystem::path installPath, bool skipGame)\n{\n    g_installPath = installPath;\n\n    EmbeddedPlayer::Init();\n    NFD_Init();\n\n    // Guarantee that one controller is initialised.\n    // We'll rely on SDL's event loop to get the controller events.\n    XAMINPUT_STATE inputState{};\n    hid::GetState(0, &inputState);\n\n    if (skipGame)\n    {\n        for (int i = 0; i < int(DLC::Count); i++)\n            g_dlcInstalled[i] = Installer::checkDLCInstall(g_installPath, DLC(i + 1));\n\n        g_firstPage = WizardPage::SelectDLC;\n    }\n\n    SetCurrentPage(g_firstPage);\n    GameWindow::SetFullscreenCursorVisibility(true);\n\n    s_isVisible = true;\n\n    while (s_isVisible)\n    {\n        Video::WaitOnSwapChain();\n        EmbeddedPlayer::PlayMusic();\n        SDL_PumpEvents();\n        SDL_FlushEvents(SDL_FIRSTEVENT, SDL_LASTEVENT);\n        GameWindow::Update();\n        Video::Present();\n    }\n\n    Fader::FadeIn(0);\n    ButtonWindow::Close();\n    GameWindow::SetFullscreenCursorVisibility(false);\n    NFD_Quit();\n    EmbeddedPlayer::Shutdown();\n    InstallerWizard::Shutdown();\n\n    return !g_isQuitting;\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/installer_wizard.h",
    "content": "#pragma once\n\nstruct InstallerWizard\n{\n    static inline bool s_isVisible = false;\n\n    static void Init();\n    static void Draw();\n    static void Shutdown();\n    static bool Run(std::filesystem::path installPath, bool skipGame);\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/message_window.cpp",
    "content": "#include \"message_window.h\"\n#include <api/Marathon.h>\n#include <patches/aspect_ratio_patches.h>\n#include <gpu/imgui/imgui_snapshot.h>\n#include <gpu/video.h>\n#include <hid/hid.h>\n#include <locale/locale.h>\n#include <ui/imgui_utils.h>\n#include <app.h>\n#include <decompressor.h>\n#include <exports.h>\n#include <sdl_listener.h>\n\nstatic bool g_isAwaitingResult{};\nstatic bool g_isClosing{};\n\nstatic int g_selectedRowIndex{};\nstatic int g_prevSelectedRowIndex{};\n\nstatic bool g_upWasHeld{};\nstatic bool g_downWasHeld{};\n\nstatic ImVec2 g_joypadAxis = {};\nstatic bool g_isAccepted{};\nstatic bool g_isDeclined{};\n\nstatic double g_time{};\n\nstatic std::string g_text{};\nstatic int g_result{};\nstatic std::vector<std::string> g_buttons{};\nstatic int g_defaultButtonIndex{};\nstatic int g_cancelButtonIndex{};\n\nclass SDLEventListenerForMessageWindow : public SDLEventListener\n{\npublic:\n    bool OnSDLEvent(SDL_Event* event) override\n    {\n        if (App::s_isInit || !MessageWindow::s_isVisible || !hid::IsInputAllowed())\n            return false;\n\n        constexpr float axisValueRange = 32767.0f;\n        constexpr float axisTapRange = 0.5f;\n\n        ImVec2 tapDirection = {};\n\n        switch (event->type)\n        {\n            case SDL_KEYDOWN:\n            {\n                switch (event->key.keysym.scancode)\n                {\n                    case SDL_SCANCODE_UP:\n                        g_joypadAxis.y = 1.0f;\n                        break;\n\n                    case SDL_SCANCODE_DOWN:\n                        g_joypadAxis.y = -1.0f;\n                        break;\n\n                    case SDL_SCANCODE_RETURN:\n                    case SDL_SCANCODE_KP_ENTER:\n                        g_isAccepted = true;\n                        break;\n\n                    case SDL_SCANCODE_ESCAPE:\n                        g_isDeclined = true;\n                        break;\n                }\n\n                break;\n            }\n\n            case SDL_MOUSEBUTTONDOWN:\n            {\n                // Only accept left mouse button.\n                if (event->button.button != SDL_BUTTON_LEFT)\n                    break;\n\n                // Only accept mouse buttons when an item is selected.\n                if (g_selectedRowIndex == -1)\n                    break;\n\n                g_isAccepted = true;\n\n                break;\n            }\n\n            case SDL_CONTROLLERBUTTONDOWN:\n            {\n                switch (event->cbutton.button)\n                {\n                    case SDL_CONTROLLER_BUTTON_DPAD_UP:\n                        g_joypadAxis = { 0.0f, 1.0f };\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_DPAD_DOWN:\n                        g_joypadAxis = { 0.0f, -1.0f };\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_A:\n                        g_isAccepted = true;\n                        break;\n\n                    case SDL_CONTROLLER_BUTTON_B:\n                        g_isDeclined = true;\n                        break;\n                }\n\n                break;\n            }\n\n            case SDL_CONTROLLERAXISMOTION:\n            {\n                if (event->caxis.axis < 2)\n                {\n                    float newAxisValue = -(event->caxis.value / axisValueRange);\n                    bool sameDirection = (newAxisValue * g_joypadAxis[event->caxis.axis]) > 0.0f;\n                    bool wasInRange = abs(g_joypadAxis[event->caxis.axis]) > axisTapRange;\n                    bool isInRange = abs(newAxisValue) > axisTapRange;\n\n                    if (sameDirection && !wasInRange && isInRange)\n                        tapDirection[event->caxis.axis] = newAxisValue;\n\n                    g_joypadAxis[event->caxis.axis] = newAxisValue;\n                }\n\n                break;\n            }\n        }\n\n        return false;\n    }\n}\ng_sdlEventListenerForMessageWindow;\n\nvoid DrawButton(int rowIndex, float yOffset, float yPadding, float width, float height, std::string& text)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto clipRectMin = drawList->GetClipRectMin();\n    auto clipRectMax = drawList->GetClipRectMax();\n\n    ImVec2 min = { clipRectMin.x + ((clipRectMax.x - clipRectMin.x) - width) / 2, clipRectMin.y + (height * rowIndex) + yOffset + (yPadding * rowIndex) };\n    ImVec2 max = { min.x + width, min.y + height };\n\n    auto textColour = IM_COL32_WHITE;\n\n    if (rowIndex == g_selectedRowIndex)\n    {\n        auto gb = 255 * BREATHE_MOTION(1.0f, 0.0f, g_time, (g_isClosing ? 0.1f : 0.9f));\n\n        textColour = IM_COL32(255, gb, gb, 255);\n\n        if (!g_isClosing)\n            DrawArrowCursor(min, g_time, false, true);\n    }\n\n    auto textSize = g_pFntRodin->CalcTextSizeA(g_fntRodinSize, FLT_MAX, 0, text.c_str());\n\n    // Show low quality text in-game.\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n    DrawTextBasic\n    (\n        g_pFntRodin,\n        g_fntRodinSize,\n        { /* X */ min.x + ((max.x - min.x) - textSize.x) / 2, /* Y */ min.y + ((max.y - min.y) - textSize.y) / 2 },\n        textColour,\n        text.c_str()\n    );\n\n    // Reset the shader modifier.\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n}\n\nstatic void ResetSelection()\n{\n    /* Always use -1 for mouse input to prevent the selection\n       cursor from erroneously appearing where it shouldn't. */\n    g_selectedRowIndex = hid::g_inputDevice == hid::EInputDevice::Mouse\n        ? -1\n        : g_defaultButtonIndex;\n\n    g_upWasHeld = false;\n    g_downWasHeld = false;\n    g_joypadAxis = {};\n    g_isAccepted = false;\n    g_isDeclined = false;\n}\n\nvoid MessageWindow::Draw()\n{\n    if (!s_isVisible)\n        return;\n\n    if (g_isClosing && (ImGui::GetTime() - g_time) > (1.0 / 60.0) * 30.0)\n    {\n        g_isAwaitingResult = false;\n        s_isVisible = false;\n        return;\n    }\n\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto isController = hid::IsInputDeviceController();\n    auto isKeyboard = hid::g_inputDevice == hid::EInputDevice::Keyboard;\n\n    // Handle controller input when the game is booted.\n    if (App::s_isInit)\n    {\n        // Always assume keyboard to prevent mouse from blocking control in-game.\n        isKeyboard = true;\n\n        if (auto& spInputManager = App::s_pApp->m_pDoc->m_vspInputManager[0])\n        {\n            auto& rPadState = spInputManager->m_PadState;\n\n            g_joypadAxis.y = -rPadState.LeftStickVertical;\n\n            if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadUp))\n                g_joypadAxis.y = 1.0f;\n\n            if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_DPadDown))\n                g_joypadAxis.y = -1.0f;\n\n            g_isAccepted = rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A);\n            g_isDeclined = rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B);\n\n            if (isKeyboard)\n                g_isAccepted = g_isAccepted || rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_Start);\n        }\n    }\n\n    ImVec2 msgMin = { g_horzCentre + Scale(96, true), g_vertCentre + Scale(96, true) };\n    ImVec2 msgMax = { msgMin.x + Scale(1088, true), msgMin.y + Scale(384, true) };\n    ImVec2 msgCentre = { (msgMin.x / 2) + (msgMax.x / 2), (msgMin.y / 2) + (msgMax.y / 2) };\n\n    DrawWindow(msgMin, msgMax);\n\n    // Use low quality text when the game is booted to not clash with existing UI.\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n    auto textMargin = Scale(270, true);\n    auto textMaxWidth = msgMax.x - msgMin.x - textMargin;\n    auto textSize = g_pFntRodin->CalcTextSizeA(g_fntRodinSize, textMaxWidth, textMaxWidth, g_text.c_str());\n\n    DrawTextParagraph\n    (\n        g_pFntRodin,\n        g_fntRodinSize,\n        textMaxWidth,\n        { msgCentre.x - textSize.x / 2, msgCentre.y - textSize.y / 2 },\n        1.0f,\n        g_text.c_str(),\n        [&](const char* str, ImVec2 pos) { DrawTextBasic(g_pFntRodin, g_fntRodinSize, pos, IM_COL32_WHITE, str); }\n    );\n\n    // Reset the shader modifier.\n    if (App::s_isInit)\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n    drawList->PopClipRect();\n\n    ImVec2 selMin = { msgMin.x, msgMax.y + ((msgMin.y - g_vertCentre) / 2) };\n    ImVec2 selMax = { msgMax.x, selMin.y + Scale(128, true) };\n\n    DrawWindow(selMin, selMax);\n\n    auto rowCount = 0;\n    auto windowMarginX = Scale(36, true);\n    auto itemWidth = msgMax.x - msgMin.x - windowMarginX;\n    auto itemHeight = Scale(25, true);\n    auto itemPadding = Scale(18, true);\n    auto windowMarginY = ((selMax.y - selMin.y) / 2) - (((itemHeight + itemPadding) / 2) * g_buttons.size());\n\n    for (auto& button : g_buttons)\n        DrawButton(rowCount++, windowMarginY, itemPadding, itemWidth, itemHeight, button);\n\n    if (isController || isKeyboard)\n    {\n        auto upIsHeld = g_joypadAxis.y > 0.5f;\n        auto downIsHeld = g_joypadAxis.y < -0.5f;\n        auto scrollUp = !g_upWasHeld && upIsHeld;\n        auto scrollDown = !g_downWasHeld && downIsHeld;\n            \n        auto prevSelectedRowIndex = g_selectedRowIndex;\n            \n        if (scrollUp)\n        {\n            if (g_selectedRowIndex > 0)\n                --g_selectedRowIndex;\n        }\n        else if (scrollDown)\n        {\n            if (g_selectedRowIndex < rowCount - 1)\n                ++g_selectedRowIndex;\n        }\n            \n        if (scrollUp || scrollDown)\n        {\n            Game_PlaySound(\"move\");\n            g_prevSelectedRowIndex = prevSelectedRowIndex;\n            g_joypadAxis.y = 0;\n        }\n            \n        g_upWasHeld = upIsHeld;\n        g_downWasHeld = downIsHeld;\n            \n        if (g_isDeclined)\n        {\n            if (g_buttons.size() == 1)\n            {\n                Game_PlaySound(\"window_close\");\n            }\n            else\n            {\n                if (g_selectedRowIndex == g_cancelButtonIndex)\n                {\n                    Game_PlaySound(\"window_close\");\n                }\n                else\n                {\n                    Game_PlaySound(\"move\");\n                }\n\n                g_selectedRowIndex = g_cancelButtonIndex;\n            }\n\n            g_isDeclined = false;\n        }\n    }\n    else if (!App::s_isInit) // Only accept mouse input during installer.\n    {\n        auto clipRectMin = drawList->GetClipRectMin();\n        auto clipRectMax = drawList->GetClipRectMax();\n\n        ImVec2 listMin = { clipRectMin.x, clipRectMin.y + windowMarginY };\n        ImVec2 listMax = { clipRectMax.x, clipRectMin.y + windowMarginY + (itemHeight * rowCount) + (itemPadding * rowCount) };\n\n        // Invalidate index if the mouse cursor is outside of the list box.\n        if (!ImGui::IsMouseHoveringRect(listMin, listMax, false))\n            g_selectedRowIndex = -1;\n\n        for (int i = 0; i < rowCount; i++)\n        {\n            auto currentHeight = itemHeight * i;\n            auto currentPadding = itemPadding * i;\n\n            ImVec2 itemMin = { listMin.x, listMin.y + currentHeight + currentPadding };\n            ImVec2 itemMax = { listMax.x, clipRectMin.y + windowMarginY + currentHeight + itemHeight + currentPadding };\n\n            if (ImGui::IsMouseHoveringRect(itemMin, itemMax, false))\n            {\n                if (g_selectedRowIndex != i)\n                    Game_PlaySound(\"move\");\n\n                g_selectedRowIndex = i;\n\n                break;\n            }\n        }\n    }\n\n    if (g_selectedRowIndex != -1 && g_isAccepted && !g_isClosing)\n    {\n        g_result = g_selectedRowIndex;\n\n        Game_PlaySound(\"main_deside\");\n\n        MessageWindow::Close();\n    }\n\n    drawList->PopClipRect();\n}\n\nbool MessageWindow::Open(std::string text, int* result, std::span<std::string> buttons, int defaultButtonIndex, int cancelButtonIndex)\n{\n    if (!g_isAwaitingResult && *result == -1)\n    {\n        s_isVisible = true;\n        g_isClosing = false;\n        g_time = ImGui::GetTime();\n\n        g_text = text;\n        g_buttons = std::vector(buttons.begin(), buttons.end());\n        g_defaultButtonIndex = defaultButtonIndex;\n        g_cancelButtonIndex = cancelButtonIndex;\n\n        if (g_buttons.empty())\n            g_buttons.push_back(Localise(\"Common_OK\"));\n\n        ResetSelection();\n\n        g_isAwaitingResult = true;\n    }\n    \n    *result = g_result;\n\n    // Returns true when the message window is closed.\n    return !g_isAwaitingResult;\n}\n\nvoid MessageWindow::Close()\n{\n    if (g_isClosing)\n        return;\n\n    g_time = ImGui::GetTime();\n    g_isClosing = true;\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/message_window.h",
    "content": "#pragma once\n\n#define MSG_OPEN   (false)\n#define MSG_CLOSED (true)\n\nclass MessageWindow\n{\npublic:\n    static inline bool s_isVisible = false;\n\n    static void Draw();\n    static bool Open(std::string text, int* result, std::span<std::string> buttons = {}, int defaultButtonIndex = 0, int cancelButtonIndex = 1);\n    static void Close();\n};\n"
  },
  {
    "path": "MarathonRecomp/ui/options_menu.cpp",
    "content": "#include \"options_menu.h\"\n#include <locale/locale.h>\n#include <patches/aspect_ratio_patches.h>\n#include <patches/audio_patches.h>\n#include <patches/MainMenuTask_patches.h>\n#include <res/images/common/main_menu9.dds.h>\n#include <ui/black_bar.h>\n#include <ui/button_window.h>\n#include <ui/fader.h>\n#include <ui/game_window.h>\n#include <ui/imgui_utils.h>\n#include <ui/message_window.h>\n#include <user/config.h>\n#include <app.h>\n#include <decompressor.h>\n#include <exports.h>\n\nstatic constexpr int MAX_VISIBLE_ROWS = 3;\n\nstatic bool g_isClosingButtonWindow{};\n\nstatic double g_stateTime{};\nstatic double g_flowStateTime{};\nstatic double g_chevronTime{};\nstatic double g_categoryTime{};\nstatic double g_cursorArrowsTime{};\nstatic double g_scrollArrowsTime{};\nstatic double g_lastIncrementTime{};\nstatic double g_lastTappedTime{};\n\nstatic bool g_up{};\nstatic bool g_upWasHeld{};\nstatic bool g_down{};\nstatic bool g_downWasHeld{};\nstatic bool g_left{};\nstatic bool g_leftWasHeld{};\nstatic bool g_right{};\nstatic bool g_rightWasHeld{};\nstatic bool g_isAccepted{};\nstatic bool g_isDeclined{};\nstatic bool g_isReset{};\n\nstatic int g_categoryIndex{ -1 };\nstatic int g_optionIndex{};\nstatic int g_optionCount{};\nstatic IConfigDef* g_optionCurrent{};\nstatic bool g_optionCanReset{};\n\nstatic std::unique_ptr<GuestTexture> g_upTexMainMenu9{};\n\nstatic std::string& GetCategoryName(OptionsMenuCategory category)\n{\n    switch (category)\n    {\n        case OptionsMenuCategory::System: return Localise(\"Options_Category_System\");\n        case OptionsMenuCategory::Input: return Localise(\"Options_Category_Input\");\n        case OptionsMenuCategory::Audio: return Localise(\"Options_Category_Audio\");\n        case OptionsMenuCategory::Video: return Localise(\"Options_Category_Video\");\n        case OptionsMenuCategory::Debug: return Localise(\"Options_Category_Debug\");\n    }\n\n    return g_localeMissing;\n}\n\nstatic std::string& GetCategoryDescription(OptionsMenuCategory category)\n{\n    switch (category)\n    {\n        case OptionsMenuCategory::System: return Localise(\"Options_Desc_Category_System\");\n        case OptionsMenuCategory::Input: return Localise(\"Options_Desc_Category_Input\");\n        case OptionsMenuCategory::Audio: return Localise(\"Options_Desc_Category_Audio\");\n        case OptionsMenuCategory::Video: return Localise(\"Options_Desc_Category_Video\");\n        case OptionsMenuCategory::Debug: return Localise(\"Options_Desc_Category_Debug\");\n    }\n\n    return g_localeMissing;\n}\n\nstatic void ResetCategorySelection()\n{\n    g_categoryIndex = -1;\n}\n\nstatic void ResetOptionSelection()\n{\n    g_optionIndex = 0;\n    g_optionCurrent = nullptr;\n    g_optionCanReset = false;\n}\n\nstatic void ResetSelection()\n{\n    auto time = ImGui::GetTime();\n\n    g_flowStateTime = time;\n\n    ResetCategorySelection();\n    ResetOptionSelection();\n}\n\nstatic bool CheckAndDiscard(bool& value)\n{\n    if (value)\n    {\n        value = false;\n        return true;\n    }\n\n    return false;\n}\n\nstatic void MoveCursor(int& cursorIndex, double& cursorTime, int min = 0, int max = INT_MAX, std::function<void()> onCursorMoved = nullptr)\n{\n    auto time = ImGui::GetTime();\n\n    auto scrollUp = g_up;\n    auto scrollDown = g_down;\n\n    if (scrollUp || scrollDown)\n        g_lastTappedTime = time;\n\n    static constexpr auto FAST_SCROLL_THRESHOLD = 0.3;\n    static constexpr auto FAST_SCROLL_SPEED = 1.0 / 6.5;\n\n    auto fastScroll = (time - g_lastTappedTime) > FAST_SCROLL_THRESHOLD;\n\n    if (fastScroll)\n    {\n        if ((time - g_lastIncrementTime) < FAST_SCROLL_SPEED)\n        {\n            fastScroll = false;\n        }\n        else\n        {\n            g_lastIncrementTime = time;\n\n            scrollUp = g_upWasHeld;\n            scrollDown = g_downWasHeld;\n        }\n    }\n\n    if (scrollUp)\n    {\n        --cursorIndex;\n\n        if (cursorIndex < min)\n            cursorIndex = max - 1;\n    }\n    else if (scrollDown)\n    {\n        ++cursorIndex;\n\n        if (cursorIndex >= max)\n            cursorIndex = min;\n    }\n\n    if (scrollUp || scrollDown)\n    {\n        Game_PlaySound(\"move\");\n\n        cursorTime = time;\n\n        if (onCursorMoved)\n            onCursorMoved();\n    }\n}\n\nstatic void DrawCategories(ImVec2 min, ImVec2 max)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    auto selectedUVs = PIXELS_TO_UV_COORDS(1024, 1024, 443, 524, 560, 47);\n    auto unselectedUVs = PIXELS_TO_UV_COORDS(1024, 1024, 443, 579, 560, 47);\n    auto categoryWidth = Scale(560, true);\n    auto categoryHeight = Scale(47, true);\n\n    if (g_categoryIndex == -1)\n    {\n        g_categoryIndex = 0;\n        OptionsMenu::s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex));\n    }\n\n    for (size_t i = 0; i < (size_t)OptionsMenuCategory::Count; i++)\n    {\n        // Don't show debug category if locked.\n        if (!OptionsMenu::s_isDebugUnlocked && (OptionsMenuCategory)i == OptionsMenuCategory::Debug)\n            continue;\n\n        auto isCurrent = i == g_categoryIndex;\n        auto categoryOffsetY = Scale(48, true) * i;\n\n        ImVec2 categoryMin = { min.x + Scale(42, true), min.y + Scale(147, true) + categoryOffsetY };\n        ImVec2 categoryMax = { categoryMin.x + categoryWidth, categoryMin.y + categoryHeight };\n\n        auto categoryMotionTime = ComputeLinearMotion(g_stateTime, 0, 5, OptionsMenu::s_state != OptionsMenuState::Idle);\n        auto categoryMotion = IM_COL32(255, 255, 255, 255 * categoryMotionTime);\n\n        SetHorizontalGradient(categoryMin, categoryMax, categoryMotion, IM_COL32_WHITE_TRANS);\n        drawList->AddImage(g_upTexMainMenu7.get(), categoryMin, categoryMax, GET_UV_COORDS(isCurrent ? selectedUVs : unselectedUVs));\n        ResetGradient();\n\n        if (isCurrent && OptionsMenu::s_flowState == OptionsMenuFlowState::CategoryCursor)\n        {\n            auto cursorOffsetX = Scale(80, true);\n            auto cursorOffsetY = Scale(8, true);\n\n            DrawArrowCursor({ categoryMin.x + cursorOffsetX, categoryMin.y + cursorOffsetY }, g_cursorArrowsTime, true, false, OptionsMenu::s_state != OptionsMenuState::Idle);\n        }\n\n        auto text = GetCategoryName((OptionsMenuCategory)i);\n        auto textSize = g_pFntRodin->CalcTextSizeA(g_fntRodinSize, FLT_MAX, 0.0f, text.c_str());\n\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n        drawList->AddText(g_pFntRodin, g_fntRodinSize, { categoryMin.x + Scale(129, true), categoryMin.y + Scale(6, true) }, categoryMotion, text.c_str());\n        SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n    }\n}\n\nvoid DrawSelectionArrows(ImVec2 min, ImVec2 max, bool isSelected)\n{\n    if (!isSelected)\n        return;\n\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    static bool s_isLeftArrowMotion = false;\n    static bool s_isRightArrowMotion = false;\n\n    auto motionTime = (s_isLeftArrowMotion || s_isRightArrowMotion)\n        ? ComputeLinearMotion(g_lastTappedTime, 0, 15)\n        : 0;\n\n    if (g_left)\n    {\n        s_isLeftArrowMotion = true;\n        s_isRightArrowMotion = false;\n    }\n\n    if (g_right)\n    {\n        s_isLeftArrowMotion = false;\n        s_isRightArrowMotion = true;\n    }\n\n    if (motionTime >= 1.0)\n    {\n        s_isLeftArrowMotion = false;\n        s_isRightArrowMotion = false;\n    }\n\n    auto arrowUVs = PIXELS_TO_UV_COORDS(256, 256, 15, 46, 28, 26);\n\n    auto arrowWidth = Scale(28, true);\n    auto arrowHeight = Scale(26, true);\n\n    auto arrowOffsetXMin = Scale(30, true);\n    auto arrowOffsetXMax = Scale(20, true);\n\n    auto arrowOffsetX = Lerp(arrowOffsetXMin, arrowOffsetXMax, sin(motionTime * M_PI));\n    auto arrowOffsetY = Scale(10, true);\n\n    auto arrowLeftOffsetX = s_isLeftArrowMotion ? arrowOffsetX : arrowOffsetXMin;\n    auto arrowRightOffsetX = s_isRightArrowMotion ? arrowOffsetX : arrowOffsetXMin;\n\n    ImVec2 arrowLeftMin = { min.x + arrowLeftOffsetX, min.y + arrowOffsetY };\n    ImVec2 arrowLeftMax = { arrowLeftMin.x + arrowWidth, arrowLeftMin.y + arrowHeight };\n    ImVec2 arrowRightMin = { max.x - arrowWidth - arrowRightOffsetX, arrowLeftMin.y };\n    ImVec2 arrowRightMax = { arrowRightMin.x + arrowWidth, arrowLeftMax.y };\n\n    // Draw left arrow.\n    AddImageFlipped(g_upTexMainMenu8.get(), arrowLeftMin, arrowLeftMax, GET_UV_COORDS(arrowUVs), IM_COL32_WHITE, true);\n\n    // Draw right arrow.\n    drawList->AddImage(g_upTexMainMenu8.get(), arrowRightMin, arrowRightMax, GET_UV_COORDS(arrowUVs));\n};\n\ntemplate <typename T, bool isHidden = false>\nstatic void DrawOption\n(\n    int rowIndex,\n    ConfigDef<T, isHidden>* config,\n    bool isAccessible,\n    std::string* inaccessibleReason = nullptr,\n    T valueMin = T(0),\n    T valueCentre = T(0.5),\n    T valueMax = T(1),\n    bool isSlider = true,\n    bool isInterpolatedString = false\n)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n    auto clipRectMin = drawList->GetClipRectMin();\n    auto clipRectMax = drawList->GetClipRectMax();\n\n    auto optionMotionTime = ComputeLinearMotion(g_categoryTime, 0, 5, OptionsMenu::s_state != OptionsMenuState::Idle);\n    auto optionColourMotion = ColourLerp(IM_COL32_WHITE_TRANS, isAccessible ? IM_COL32_WHITE : IM_COL32(137, 137, 137, 255), optionMotionTime);\n    auto optionHeight = Scale(106, true);\n    auto offsetScroll = 0.0f;\n\n    auto isMiddleRow = false;\n\n    // Only scroll if page has more than three items.\n    if (g_optionCount > MAX_VISIBLE_ROWS)\n    {\n        isMiddleRow = ((rowIndex - std::max(g_optionIndex - 1, 0)) % MAX_VISIBLE_ROWS) >= 1;\n\n        if (g_optionIndex >= g_optionCount - (MAX_VISIBLE_ROWS - 1))\n        {\n            // Stop scrolling near bottom to use cursor instead.\n            offsetScroll = -(g_optionCount - MAX_VISIBLE_ROWS) * optionHeight;\n            isMiddleRow = ((rowIndex - std::max(g_optionCount - MAX_VISIBLE_ROWS, 0)) % MAX_VISIBLE_ROWS) >= 1;\n        }\n        else if (g_optionIndex >= 1)\n        {\n            // Start scrolling from the middle item.\n            offsetScroll = -(g_optionIndex - 1) * optionHeight;\n        }\n    }\n    else\n    {\n        isMiddleRow = rowIndex >= 1;\n    }\n\n    auto offsetY = optionHeight * rowIndex + offsetScroll;\n    auto isCurrent = g_optionIndex == rowIndex;\n    auto isSelected = isCurrent && OptionsMenu::s_flowState == OptionsMenuFlowState::OptionSelected;\n\n    auto bgEdgeUVs = PIXELS_TO_UV_COORDS(256, 256, 1, 0, 51, 45);\n    auto bgStretchUVs = PIXELS_TO_UV_COORDS(256, 256, 51, 0, 51, 45);\n    auto bgWidth = Scale(50, true);\n    auto bgHeight = Scale(45, true);\n    auto bgColourMotion = isSelected ? 255 : 0;\n    auto bgColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 245 * optionMotionTime);\n    auto bgFadeColour = IM_COL32(bgColourMotion, bgColourMotion, bgColourMotion, 45 * optionMotionTime);\n\n    ImVec2 titleBgEdgeMin = { clipRectMin.x, clipRectMin.y + offsetY };\n    ImVec2 titleBgEdgeMax = { titleBgEdgeMin.x + bgWidth, titleBgEdgeMin.y + bgHeight };\n    ImVec2 titleBgStretchMin = { titleBgEdgeMax.x, titleBgEdgeMin.y };\n    ImVec2 titleBgStretchMax = { clipRectMax.x, titleBgEdgeMax.y };\n    ImVec2 titleBgFadeMin = { titleBgStretchMax.x - Scale(300, true), titleBgStretchMax.y };\n    ImVec2 titleBgFadeMax = titleBgStretchMax;\n\n    SetHorizontalGradient(titleBgFadeMin, titleBgFadeMax, bgColour, bgFadeColour);\n    drawList->AddImage(g_upTexMainMenu8.get(), titleBgEdgeMin, titleBgEdgeMax, GET_UV_COORDS(bgEdgeUVs), bgColour);\n    drawList->AddImage(g_upTexMainMenu8.get(), titleBgStretchMin, titleBgStretchMax, GET_UV_COORDS(bgStretchUVs), bgColour);\n    ResetGradient();\n\n    auto titleText = config->GetNameLocalised(Config::Language);\n    auto titleTextSize = g_pFntRodin->CalcTextSizeA(g_fntRodinSize, FLT_MAX, 0, titleText.c_str());\n\n    auto titleFadeRightScale = Scale(40, true);\n    auto titleFadeRightOffsetX = Scale(15, true);\n    auto scrollArrowOffsetX = clipRectMax.x - Scale(84, true) - BlackBar::s_pillarboxWidth;\n\n    ImVec2 titlePos = { titleBgEdgeMin.x + Scale(51, true), titleBgEdgeMin.y + Scale(8, true) };\n    ImVec2 titleSafeAreaMin = { titleBgEdgeMax.x, titleBgEdgeMin.y };\n    ImVec2 titleSafeAreaMax = { scrollArrowOffsetX - titleFadeRightOffsetX, titleBgEdgeMax.y };\n\n    // Recentre title vertically for larger Japanese font.\n    if (Config::Language == ELanguage::Japanese)\n        titlePos.y -= Scale(0.5, true);\n\n    auto isTitleRightFade = !isMiddleRow && OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor;\n\n    // Don't fade right side of title if it fits within the safe area.\n    if (titleSafeAreaMax.x - titleSafeAreaMin.x >= titleTextSize.x)\n        isTitleRightFade = false;\n\n    if (isTitleRightFade)\n    {\n        ImVec2 titleFadeRightMin = { titleSafeAreaMax.x - titleFadeRightScale, titleSafeAreaMin.y };\n        ImVec2 titleFadeRightMax = { titleFadeRightMin.x + titleFadeRightScale, titleSafeAreaMax.y };\n\n        SetHorizontalGradient(titleFadeRightMin, titleFadeRightMax, IM_COL32_WHITE, IM_COL32_WHITE_TRANS);\n    }\n\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n    drawList->AddText(g_pFntRodin, g_fntRodinSize, titlePos, optionColourMotion, titleText.c_str());\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n\n    if (isTitleRightFade)\n        ResetGradient();\n\n    if (isCurrent)\n    {\n        if (OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor)\n        {\n            if (config != g_optionCurrent)\n            {\n                auto reason = inaccessibleReason ? *inaccessibleReason : \"DUMMY\";\n\n                OptionsMenu::s_commonMenu.SetDescription(isAccessible ? config->GetDescription(Config::Language) : reason);\n            }\n\n            DrawArrowCursor({ titleBgEdgeMin.x + Scale(8, true), titleBgEdgeMin.y + Scale(9, true) }, g_cursorArrowsTime, true, false, OptionsMenu::s_state != OptionsMenuState::Idle);\n\n            g_optionCurrent = config;\n            g_optionCanReset = isAccessible && config->GetName().find(\"Language\") == std::string::npos && (void*)config != (void*)&Config::WindowSize;\n        }\n        else\n        {\n            g_optionCurrent = nullptr;\n            g_optionCanReset = false;\n        }\n    }\n\n    auto ctrlBgOffsetXMotion = Lerp(Scale(30, true), Scale(5, true), optionMotionTime);\n    auto ctrlBgColourMotion = ColourLerp(IM_COL32_WHITE_TRANS, optionColourMotion, optionMotionTime);\n\n    ImVec2 ctrlBgLeftEdgeMin = { titleBgEdgeMin.x + ctrlBgOffsetXMotion, titleBgEdgeMin.y + Scale(54, true) };\n    ImVec2 ctrlBgLeftEdgeMax = { ctrlBgLeftEdgeMin.x + bgWidth, ctrlBgLeftEdgeMin.y + bgHeight };\n    ImVec2 ctrlBgStretchMin = { ctrlBgLeftEdgeMax.x, ctrlBgLeftEdgeMin.y };\n    ImVec2 ctrlBgStretchMax = { ctrlBgStretchMin.x + Scale(300, true), ctrlBgLeftEdgeMax.y };\n    ImVec2 ctrlBgRightEdgeMin = { ctrlBgStretchMax.x, ctrlBgStretchMin.y };\n    ImVec2 ctrlBgRightEdgeMax = { ctrlBgRightEdgeMin.x + bgWidth, ctrlBgRightEdgeMin.y + bgHeight };\n\n    ImVec2 ctrlBgCentre =\n    {\n        ctrlBgLeftEdgeMin.x + ((ctrlBgRightEdgeMax.x - ctrlBgLeftEdgeMin.x) / 2),\n        ctrlBgLeftEdgeMin.y + ((ctrlBgLeftEdgeMax.y - ctrlBgLeftEdgeMin.y) / 2)\n    };\n\n    drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgLeftEdgeMin, ctrlBgLeftEdgeMax, GET_UV_COORDS(bgEdgeUVs), ctrlBgColourMotion);\n    drawList->AddImage(g_upTexMainMenu8.get(), ctrlBgStretchMin, ctrlBgStretchMax, GET_UV_COORDS(bgStretchUVs), ctrlBgColourMotion);\n    AddImageFlipped(g_upTexMainMenu8.get(), ctrlBgRightEdgeMin, ctrlBgRightEdgeMax, GET_UV_COORDS(bgEdgeUVs), ctrlBgColourMotion, true);\n\n    constexpr auto isSliderType = std::is_same_v<T, float> || std::is_same_v<T, int>;\n\n    if constexpr (isSliderType)\n    {\n        if (isSlider)\n        {\n            auto gaugeOffsetX = Scale(41, true);\n            auto gaugeHeight = Scale(11, true);\n\n            ImVec2 gaugeMin = { ctrlBgLeftEdgeMin.x + gaugeOffsetX, (ctrlBgRightEdgeMin.y + bgHeight / 2) - (gaugeHeight / 2) + Scale(1, true) };\n            ImVec2 gaugeMax = { ctrlBgRightEdgeMax.x - gaugeOffsetX, gaugeMin.y + gaugeHeight / 2 };\n\n            drawList->AddRectFilled(gaugeMin, gaugeMax, IM_COL32(0, 0, 0, 255 * optionMotionTime), Scale(10, true));\n\n            auto handleUVs = PIXELS_TO_UV_COORDS(256, 256, 72, 49, 49, 19);\n            auto handleWidth = Scale(49, true);\n            auto handleHeight = Scale(19, true);\n            auto handleOffsetX = Scale(12, true);\n            auto handleOffsetFactor = 0.0f;\n\n            if (config->Value <= valueCentre)\n            {\n                handleOffsetFactor = float(config->Value - valueMin) / (valueCentre - valueMin) * 0.5f;\n            }\n            else\n            {\n                handleOffsetFactor = 0.5f + float(config->Value - valueCentre) / (valueMax - valueCentre) * 0.5f;\n            }\n\n            handleOffsetX = ((gaugeMin.x + handleOffsetX) + ((gaugeMax.x - handleOffsetX) - (gaugeMin.x + handleOffsetX)) * handleOffsetFactor) - (handleWidth / 2);\n\n            ImVec2 handleMin = { handleOffsetX, gaugeMin.y - (handleHeight / 2) + Scale(4.5, true) };\n            ImVec2 handleMax = { handleMin.x + handleWidth, handleMin.y + handleHeight };\n\n            drawList->AddImage(g_upTexMainMenu8.get(), handleMin, handleMax, GET_UV_COORDS(handleUVs), optionColourMotion);\n        }\n        else\n        {\n            DrawSelectionArrows(ctrlBgLeftEdgeMin, ctrlBgRightEdgeMax, isSelected);\n        }\n    }\n    else\n    {\n        DrawSelectionArrows(ctrlBgLeftEdgeMin, ctrlBgRightEdgeMax, isSelected);\n    }\n\n    if (isCurrent)\n    {\n        auto setValueDescription = [=]()\n        {\n            auto valueDescription = config->GetValueDescription(Config::Language);\n            auto isLanguageOption = (ConfigDef<ELanguage>*)config == &Config::Language;\n\n            if (valueDescription.empty())\n            {\n                OptionsMenu::s_commonMenu.SetDescription(config->GetDescription(Config::Language), !isLanguageOption);\n            }\n            else\n            {\n                OptionsMenu::s_commonMenu.SetDescription(valueDescription, !isLanguageOption);\n            }\n        };\n\n        if (isAccessible)\n        {\n            static T s_oldValue;\n\n            if (CheckAndDiscard(g_isAccepted))\n            {\n                Game_PlaySound(\"main_deside\");\n\n                if (OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor)\n                {\n                    setValueDescription();\n\n                    s_oldValue = config->Value;\n\n                    if (config->LockCallback)\n                        config->LockCallback(config);\n\n                    OptionsMenu::SetFlowState(OptionsMenuFlowState::OptionSelected);\n\n                    isSelected = true;\n                }\n                else\n                {\n                    if (config->Value != s_oldValue)\n                    {\n                        VideoConfigValueChangedCallback(config);\n\n                        if (config->ApplyCallback)\n                            config->ApplyCallback(config);\n                    }\n\n                    OptionsMenu::SetFlowState(OptionsMenuFlowState::OptionCursor);\n\n                    isSelected = false;\n                }\n            }\n\n            if (CheckAndDiscard(g_isDeclined))\n            {\n                Game_PlaySound(\"window_close\");\n\n                if (config->Value != s_oldValue)\n                {\n                    config->Value = s_oldValue;\n\n                    VideoConfigValueChangedCallback(config);\n\n                    if (config->Callback)\n                        config->Callback(config);\n\n                    if (config->ApplyCallback)\n                        config->ApplyCallback(config);\n                }\n\n                OptionsMenu::SetFlowState(OptionsMenuFlowState::OptionCursor);\n\n                isSelected = false;\n            }\n\n            if (g_optionCanReset && CheckAndDiscard(g_isReset))\n            {\n                Game_PlaySound(\"window_close\");\n\n                if (!config->IsDefaultValue())\n                {\n                    config->MakeDefault();\n\n                    VideoConfigValueChangedCallback(config);\n\n                    if (config->Callback)\n                        config->Callback(config);\n\n                    if (config->ApplyCallback)\n                        config->ApplyCallback(config);\n                }\n            }\n\n            if (isSelected)\n            {\n                auto increment = g_right;\n                auto decrement = g_left;\n\n                if (increment || decrement)\n                    g_lastTappedTime = ImGui::GetTime();\n\n                if constexpr (std::is_enum_v<T>)\n                {\n                    auto it = config->EnumTemplateReverse.find(config->Value);\n\n                    if (decrement)\n                    {\n                        if (it == config->EnumTemplateReverse.begin())\n                            it = config->EnumTemplateReverse.end();\n\n                        --it;\n                    }\n                    else if (increment)\n                    {\n                        ++it;\n\n                        if (it == config->EnumTemplateReverse.end())\n                            it = config->EnumTemplateReverse.begin();\n                    }\n\n                    config->Value = it->first;\n                    config->SnapToNearestAccessibleValue(increment);\n\n                    if (increment || decrement)\n                    {\n                        setValueDescription();\n\n                        Game_PlaySound(\"move\");\n\n                        if (config->Callback)\n                            config->Callback(config);\n                    }\n                }\n                else if constexpr (isSliderType)\n                {\n                    auto time = ImGui::GetTime();\n                    auto fastIncrement = isSlider && (g_leftWasHeld || g_rightWasHeld) && (time - g_lastTappedTime) > 0.5;\n                    auto playIncrementSound = true;\n\n                    static auto s_fastIncrementHoldTime = 0.0;\n                    static auto s_lastIncrementSoundTime = 0.0;\n\n                    static constexpr auto INCREMENT_TIME = 1.0 / 60.0;\n                    static constexpr auto INCREMENT_SOUND_TIME = 1.0 / 20.0;\n\n                    if (fastIncrement)\n                    {\n                        s_fastIncrementHoldTime += App::s_deltaTime;\n                    }\n                    else\n                    {\n                        s_fastIncrementHoldTime = 0.0;\n                    }\n\n                    if (fastIncrement)\n                    {\n                        playIncrementSound = (time - s_lastIncrementSoundTime) > INCREMENT_SOUND_TIME;\n\n                        if (s_fastIncrementHoldTime < INCREMENT_TIME)\n                        {\n                            fastIncrement = false;\n                        }\n                        else\n                        {\n                            g_lastIncrementTime = time;\n                        }\n                    }\n\n                    if (fastIncrement)\n                    {\n                        increment = g_rightWasHeld;\n                        decrement = g_leftWasHeld;\n                    }\n\n                    do\n                    {\n                        if constexpr (std::is_integral_v<T>)\n                        {\n                            if (decrement)\n                                config->Value -= 1;\n                            else if (increment)\n                                config->Value += 1;\n                        }\n                        else\n                        {\n                            if (decrement)\n                                config->Value -= 0.01f;\n                            else if (increment)\n                                config->Value += 0.01f;\n                        }\n\n                        if (fastIncrement)\n                            s_fastIncrementHoldTime -= INCREMENT_TIME;\n                    }\n                    while (fastIncrement && s_fastIncrementHoldTime >= INCREMENT_TIME);\n\n                    auto isValueInBounds = config->Value >= valueMin && config->Value <= valueMax;\n\n                    if ((increment || decrement) && isValueInBounds && playIncrementSound)\n                    {\n                        Game_PlaySound(\"move\");\n\n                        s_lastIncrementSoundTime = time;\n                    }\n\n                    config->Value = std::clamp(config->Value, valueMin, valueMax);\n                }\n                else if constexpr (std::is_same_v<T, bool>)\n                {\n                    if (increment || decrement)\n                    {\n                        Game_PlaySound(\"move\");\n\n                        config->Value = !config->Value;\n                    }\n                }\n\n                // Run standard callback if there's no callback on apply.\n                if (!config->ApplyCallback)\n                {\n                    if ((increment || decrement) && config->Callback)\n                        config->Callback(config);\n                }\n            }\n\n            // Toggle BGM for master and music volume sliders.\n            if (OptionsMenu::s_pBgmCue)\n                OptionsMenu::s_pBgmCue->SetPause(!(isSelected && ((ConfigDef<float>*)config == &Config::MasterVolume || (ConfigDef<float>*)config == &Config::MusicVolume)));\n        }\n        else\n        {\n            if (CheckAndDiscard(g_isAccepted))\n                Game_PlaySound(\"cannot_deside\");\n        }\n    }\n\n    std::string valueText;\n    auto isValueCentred = false;\n\n    if constexpr (std::is_same_v<T, float>)\n    {\n        valueText = fmt::format(\"{}%\", int(round(config->Value * 100.0f)));\n    }\n    else if constexpr (std::is_same_v<T, int32_t>)\n    {\n        if (config == &Config::WindowSize)\n        {\n            if (Config::Fullscreen)\n            {\n                int displayW, displayH;\n\n                GameWindow::GetSizeInPixels(&displayW, &displayH);\n\n                valueText = fmt::format(\"{}x{}\", displayW, displayH);\n            }\n            else\n            {\n                auto displayModes = GameWindow::GetDisplayModes();\n\n                if (config->Value >= 0 && config->Value < displayModes.size())\n                {\n                    auto& displayMode = displayModes[config->Value];\n\n                    valueText = fmt::format(\"{}x{}\", displayMode.w, displayMode.h);\n                }\n                else\n                {\n                    valueText = fmt::format(\"{}x{}\", GameWindow::s_width, GameWindow::s_height);\n                }\n            }\n\n            isValueCentred = true;\n        }\n        else if (config == &Config::Monitor)\n        {\n            valueText = fmt::format(\"{}\", config->Value + 1);\n            isValueCentred = true;\n        }\n        else\n        {\n            valueText = fmt::format(\"{}\", config->Value);\n\n            if (isSlider && config->Value >= valueMax)\n                valueText = Localise(\"Options_Value_Max\");\n        }\n    }\n    else\n    {\n        valueText = config->GetValueLocalised(Config::Language);\n        isValueCentred = true;\n    }\n\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_LOW_QUALITY_TEXT);\n\n    if (isInterpolatedString)\n    {\n        auto interpData = GetHidInterpTextData();\n        auto valueTextSize = MeasureInterpolatedText(g_pFntRodin, g_fntRodinSize, valueText.c_str(), &interpData);\n\n        ImVec2 valuePos = { ctrlBgCentre.x - (valueTextSize.x / 2), ctrlBgCentre.y - (valueTextSize.y / 2) - Scale(2, true) };\n\n        // Align text to right side of the background.\n        if (!isValueCentred)\n            valuePos.x = ctrlBgRightEdgeMax.x + Scale(10, true);\n\n        DrawInterpolatedText(g_pFntRodin, g_fntRodinSize, valuePos, optionColourMotion, valueText.data(), &interpData);\n    }\n    else\n    {\n        auto valueTextSize = g_pFntRodin->CalcTextSizeA(g_fntRodinSize, FLT_MAX, 0.0f, valueText.data());\n\n        ImVec2 valuePos = { ctrlBgCentre.x - (valueTextSize.x / 2), ctrlBgCentre.y - (valueTextSize.y / 2) - Scale(2, true) };\n\n        // Align text to right side of the background.\n        if (!isValueCentred)\n            valuePos.x = ctrlBgRightEdgeMax.x + Scale(10, true);\n\n        drawList->AddText(g_pFntRodin, g_fntRodinSize, valuePos, optionColourMotion, valueText.data());\n    }\n\n    SetShaderModifier(IMGUI_SHADER_MODIFIER_NONE);\n}\n\nstatic void DrawOptions(ImVec2 min, ImVec2 max)\n{\n    auto drawList = ImGui::GetBackgroundDrawList();\n\n    drawList->PushClipRect(min, max);\n\n    auto rowCount = 0;\n    auto cmnReason = &Localise(\"Options_Desc_NotAvailable\");\n    auto devReason = &Localise(\"Options_Desc_NotImplemented\");\n\n#define ENUM_VALUE(type) (type)0, (type)0, (type)0\n\n    switch ((OptionsMenuCategory)g_categoryIndex)\n    {\n        case OptionsMenuCategory::System:\n            DrawOption(rowCount++, &Config::Language, !OptionsMenu::s_isPause, cmnReason);\n            DrawOption(rowCount++, &Config::VoiceLanguage, !OptionsMenu::s_isPause, cmnReason);\n            DrawOption(rowCount++, &Config::Subtitles, true);\n            DrawOption(rowCount++, &Config::Hints, true);\n            DrawOption(rowCount++, &Config::ControlTutorial, true);\n            DrawOption(rowCount++, &Config::Autosave, true);\n            DrawOption(rowCount++, &Config::AchievementNotifications, true);\n            break;\n\n        case OptionsMenuCategory::Input:\n            DrawOption(rowCount++, &Config::HorizontalCamera, true);\n            DrawOption(rowCount++, &Config::VerticalCamera, true);\n            DrawOption(rowCount++, &Config::AllowBackgroundInput, true);\n            DrawOption(rowCount++, &Config::ControllerIcons, !OptionsMenu::s_isPause, cmnReason);\n            DrawOption(rowCount++, &Config::LightDash, true, nullptr, ENUM_VALUE(ELightDash), false, true);\n            DrawOption(rowCount++, &Config::SlidingAttack, true, nullptr, ENUM_VALUE(ESlidingAttack), false, true);\n            break;\n\n        case OptionsMenuCategory::Audio:\n            DrawOption(rowCount++, &Config::MasterVolume, true);\n            DrawOption(rowCount++, &Config::MusicVolume, true);\n            DrawOption(rowCount++, &Config::EffectsVolume, true);\n            DrawOption(rowCount++, &Config::ChannelConfiguration, !OptionsMenu::s_isPause, cmnReason);\n            DrawOption(rowCount++, &Config::MuteOnFocusLost, true);\n            DrawOption(rowCount++, &Config::MusicAttenuation, AudioPatches::CanAttenuate(), &Localise(\"Options_Desc_OSNotSupported\"));\n            break;\n\n        case OptionsMenuCategory::Video:\n        {\n            // TODO: implement buffer resize.\n            DrawOption(rowCount++, &Config::WindowSize, false, devReason, 0, 0, 1, false);\n\n            // DrawOption(rowCount++, &Config::WindowSize, !Config::Fullscreen,\n            //     &Localise(\"Options_Desc_NotAvailableFullscreen\"),\n            //     0, 0, (int)GameWindow::GetDisplayModes().size() - 1, false);\n\n            auto displayCount = GameWindow::GetDisplayCount();\n            auto canChangeMonitor = Config::Fullscreen && displayCount > 1;\n            auto monitorReason = &Localise(\"Options_Desc_NotAvailableWindowed\");\n\n            if (Config::Fullscreen && displayCount <= 1)\n                monitorReason = &Localise(\"Options_Desc_NotAvailableHardware\");\n\n            DrawOption(rowCount++, &Config::Monitor, false, devReason, 0, 0, 1, false);                // TODO: implement buffer resize. DrawOption(rowCount++, &Config::Monitor, canChangeMonitor, monitorReason, 0, 0, displayCount - 1, false);\n            DrawOption(rowCount++, &Config::AspectRatio, false, devReason);                            // TODO: implement buffer resize. DrawOption(rowCount++, &Config::AspectRatio, true);\n            DrawOption(rowCount++, &Config::ResolutionScale, false, devReason);                        // TODO: implement buffer resize. DrawOption(rowCount++, &Config::ResolutionScale, true, nullptr, 0.25f, 1.0f, 2.0f);\n            DrawOption(rowCount++, &Config::Fullscreen, false, devReason);                             // TODO: implement buffer resize. DrawOption(rowCount++, &Config::Fullscreen, true);\n            DrawOption(rowCount++, &Config::VSync, true);\n            DrawOption(rowCount++, &Config::FPS, true, nullptr, FPS_MIN, 120, FPS_MAX);\n            DrawOption(rowCount++, &Config::Brightness, true);\n            DrawOption(rowCount++, &Config::AntiAliasing, false, devReason);                           // TODO: implement MSAA.          DrawOption(rowCount++, &Config::AntiAliasing, Config::AntiAliasing.InaccessibleValues.size() != 3, &Localise(\"Options_Desc_NotAvailableHardware\"));\n            DrawOption(rowCount++, &Config::TransparencyAntiAliasing, false, devReason);               // TODO: implement MSAA.          DrawOption(rowCount++, &Config::TransparencyAntiAliasing, Config::AntiAliasing != EAntiAliasing::Off, &Localise(\"Options_Desc_NotAvailableMSAA\"));\n            DrawOption(rowCount++, &Config::ShadowResolution, !OptionsMenu::s_isPause, cmnReason);     // TODO: allow changes on demand. DrawOption(rowCount++, &Config::ShadowResolution, true);    \n            DrawOption(rowCount++, &Config::ReflectionResolution, !OptionsMenu::s_isPause, cmnReason); // TODO: allow changes on demand. DrawOption(rowCount++, &Config::ReflectionResolution, true);\n            DrawOption(rowCount++, &Config::RadialBlur, true);\n            DrawOption(rowCount++, &Config::CutsceneAspectRatio, true);\n            DrawOption(rowCount++, &Config::UIAlignmentMode, true);\n\n            break;\n        }\n\n        case OptionsMenuCategory::Debug:\n        {\n            for (auto def : g_configDefinitions)\n            {\n                if (def->GetSection() != \"Codes\")\n                    continue;\n\n                def->SetHidden(false);\n\n                DrawOption(rowCount++, (ConfigDef<bool, true>*)def, true);\n            }\n\n            break;\n        }\n    }\n\n#undef ENUM_VALUE\n\n    g_optionCount = rowCount;\n\n    drawList->PopClipRect();\n\n    if (g_optionCount > 3 && OptionsMenu::s_flowState == OptionsMenuFlowState::OptionCursor)\n    {\n        ImVec2 scrollArrowsMin = { max.x - BlackBar::s_pillarboxWidth - Scale(84, true), min.y + Scale(12, true) };\n        ImVec2 scrollArrowsMax = { max.x, max.y - Scale(16, true) };\n\n        DrawScrollArrows(scrollArrowsMin, scrollArrowsMax, Scale(20, true), g_scrollArrowsTime, g_optionIndex > 1, g_optionIndex < rowCount - 2);\n    }\n}\n\nstatic void DrawContainer(ImVec2 min, ImVec2 max)\n{\n    auto* drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    auto containerUVs = PIXELS_TO_UV_COORDS(1024, 1024, 1, 6, 736, 432);\n    auto containerWidth = Scale(716, true);\n    auto containerHeight = Scale(412, true);\n    auto containerOffsetX = Scale(73, true);\n    auto containerOffsetY = Scale(136, true);\n\n    ImVec2 containerMin = { max.x - containerWidth - containerOffsetX, min.y + containerOffsetY };\n    ImVec2 containerMax = { containerMin.x + containerWidth, containerMin.y + containerHeight };\n\n    auto containerAlphaMotionTime = ComputeLinearMotion(g_stateTime, 0, 5, OptionsMenu::s_state != OptionsMenuState::Idle);\n    auto containerAlphaMotion = IM_COL32(255, 255, 255, 255 * containerAlphaMotionTime);\n\n    SetGradient(containerMin, containerMax, containerAlphaMotion, containerAlphaMotion, IM_COL32_WHITE_TRANS, IM_COL32(255, 255, 255, 20 * containerAlphaMotionTime));\n    drawList->AddImage(g_upTexMainMenu9.get(), containerMin, containerMax, GET_UV_COORDS(containerUVs));\n    ResetGradient();\n\n    auto optionsOffsetX = Scale(170, true);\n    auto optionsOffsetY = Scale(23, true);\n\n    DrawOptions({ containerMin.x + optionsOffsetX, containerMin.y + optionsOffsetY }, { res.x, containerMax.y - optionsOffsetY });\n}\n\nvoid OptionsMenu::Draw()\n{\n    if (!s_isVisible)\n    {\n        if (g_isClosingButtonWindow)\n        {\n            ButtonWindow::Close();\n            g_isClosingButtonWindow = false;\n        }\n\n        return;\n    }\n\n    auto* drawList = ImGui::GetBackgroundDrawList();\n    auto& res = ImGui::GetIO().DisplaySize;\n\n    ImVec2 min = { g_horzCentre + g_aspectRatioNarrowMargin, g_vertCentre };\n    ImVec2 max = { res.x - min.x, res.y - min.y };\n\n    auto alphaMotionTime = s_isPause ? ComputeLinearMotion(g_stateTime, 0, 10, s_state == OptionsMenuState::Closing) : 0.0;\n    auto alpha = s_isPause ? Lerp(0, 175, alphaMotionTime) : 255;\n    auto gradientTop = IM_COL32(0, 103, 255, alpha);\n    auto gradientBottom = IM_COL32(0, 41, 100, alpha);\n\n    auto drawBackground = [=]()\n    {\n        drawList->AddRectFilledMultiColor({ 0.0f, g_vertCentre }, { res.x, res.y - g_vertCentre }, gradientTop, gradientTop, gradientBottom, gradientBottom);\n    };\n\n    if (s_isPause)\n    {\n        drawBackground();\n    }\n    else\n    {\n        auto horzMargin = Scale(128, true);\n\n        ImVec2 footerClipMin = { g_horzCentre + horzMargin, res.y - g_vertCentre - Scale(152, true) };\n        ImVec2 footerClipMax = { res.x - g_horzCentre - horzMargin, res.y - g_vertCentre - Scale(107, true) };\n\n        drawList->PushClipRect(footerClipMin, footerClipMax);\n        drawBackground();\n        drawList->PopClipRect();\n    }\n\n    auto upIsHeld = false;\n    auto downIsHeld = false;\n    auto leftIsHeld = false;\n    auto rightIsHeld = false;\n\n    switch (s_state)\n    {\n        case OptionsMenuState::Opening:\n            s_commonMenu.Open();\n            s_state = OptionsMenuState::Idle;\n            break;\n\n        case OptionsMenuState::Idle:\n        {\n            if (s_commonMenu.IsOpen())\n            {\n                for (auto& spInputManager : App::s_pApp->m_pDoc->m_vspInputManager)\n                {\n                    auto& rPadState = spInputManager->m_PadState;\n\n                    if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadUp) || -rPadState.LeftStickVertical > 0.5f)\n                        upIsHeld = true;\n\n                    if (!g_upWasHeld && upIsHeld)\n                        g_up = true;\n\n                    if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadDown) || -rPadState.LeftStickVertical < -0.5f)\n                        downIsHeld = true;\n\n                    if (!g_downWasHeld && downIsHeld)\n                        g_down = true;\n\n                    if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadLeft) || -rPadState.LeftStickHorizontal > 0.5f)\n                        leftIsHeld = true;\n\n                    if (!g_leftWasHeld && leftIsHeld)\n                        g_left = true;\n\n                    if (rPadState.IsDown(Sonicteam::SoX::Input::KeyState_DPadRight) || -rPadState.LeftStickHorizontal < -0.5f)\n                        rightIsHeld = true;\n\n                    if (!g_rightWasHeld && rightIsHeld)\n                        g_right = true;\n\n                    if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_A))\n                        g_isAccepted = true;\n\n                    if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_B))\n                        g_isDeclined = true;\n\n                    if (rPadState.IsPressed(Sonicteam::SoX::Input::KeyState_X))\n                        g_isReset = true;\n                }\n            }\n\n            if (s_isPause)\n                DrawArrows({ 0, 0 }, res, g_chevronTime);\n\n            auto buttons = s_flowState == OptionsMenuFlowState::OptionCursor && g_optionCanReset\n                ? \"Button_ResetSelectBack\"\n                : \"Button_SelectBack\";\n\n            ButtonWindow::Open(buttons, false);\n\n            break;\n        }\n\n        case OptionsMenuState::Closing:\n        {\n            auto closingTime = s_commonMenu.Close();\n\n            static bool s_isProcessedMessages{};\n\n            if (closingTime >= 0.5)\n            {\n                if (closingTime >= 1.0)\n                {\n                    MainMenuTaskPatches::s_hideButtonWindow = false;\n                    g_isClosingButtonWindow = true;\n                    s_isProcessedMessages = false;\n                    s_pMainMenuTask = nullptr;\n                    s_isVisible = false;\n                    break;\n                }\n\n                if (!s_isProcessedMessages && s_pMainMenuTask)\n                {\n                    guest_stack_var<Sonicteam::Message::HUDMainMenu::MsgSetCursor> msgSetCursor\n                    (\n                        Sonicteam::HUDMainMenu::HUDMainMenuState_MainCursorIntro,\n                        s_pMainMenuTask->m_MainMenuSelectedIndex\n                    );\n\n                    // Play cursor intro animation.\n                    s_pMainMenuTask->m_pHUDMainMenu->ProcessMessage(msgSetCursor.get());\n\n                    guest_stack_var<Sonicteam::Message::HUDMainMenu::MsgTransition> msgTransition\n                    (\n                        Sonicteam::HUDMainMenu::HUDMainMenuState_OptionsIntro, 3\n                    );\n\n                    // Play options -> main menu transition.\n                    s_pMainMenuTask->m_pHUDMainMenu->ProcessMessage(msgTransition.get());\n\n                    s_isProcessedMessages = true;\n                }\n            }\n\n            break;\n        }\n\n        case OptionsMenuState::Restarting:\n        {\n            static bool s_restartPromptOpened = false;\n            static bool s_restartFaderBegun = false;\n            static int s_restartMessageResult = -1;\n\n            auto message = Localise(\"Options_Message_Restart\");\n            std::array<std::string, 2> options = { Localise(\"Common_Yes\"), Localise(\"Common_No\") };\n\n            if (!s_restartPromptOpened)\n            {\n                // Fade out description.\n                s_commonMenu.SetDescription(\" \");\n\n                for (auto& def : g_configDefinitions)\n                {\n                    if (def->RequiresRestart() && def->IsValueChanged())\n                        message += \"\\n- \" + def->GetNameLocalised(Config::Language);\n                }\n\n                s_restartPromptOpened = true;\n            }\n\n            if (!s_restartFaderBegun && MessageWindow::Open(message, &s_restartMessageResult, options) == MSG_CLOSED)\n            {\n                if (s_restartMessageResult == 0)\n                {\n                    Fader::FadeOut(1, []() { App::Restart({ \"--use-cwd --skip-logos\" }); });\n                    s_restartFaderBegun = true;\n                }\n                else\n                {\n                    s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex));\n                    s_state = OptionsMenuState::Opening;\n                }\n\n                s_restartPromptOpened = false;\n                s_restartMessageResult = -1;\n            }\n\n            break;\n        }\n    }\n\n    switch (s_flowState)\n    {\n        case OptionsMenuFlowState::CategoryCursor:\n        {\n            auto categoryCount = (int)OptionsMenuCategory::Count;\n\n            // Remove debug category from cursor select.\n            if (!s_isDebugUnlocked)\n                categoryCount -= 1;\n\n            MoveCursor(g_categoryIndex, g_flowStateTime, 0, categoryCount, []()\n            {\n                ResetOptionSelection();\n\n                g_categoryTime = ImGui::GetTime();\n                g_cursorArrowsTime = g_categoryTime;\n                s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex));\n            });\n\n            if (CheckAndDiscard(g_isAccepted))\n            {\n                Game_PlaySound(\"main_deside\");\n\n                SetFlowState(OptionsMenuFlowState::OptionCursor);\n\n                g_cursorArrowsTime = ImGui::GetTime();\n                g_scrollArrowsTime = g_cursorArrowsTime;\n            }\n\n            if (CheckAndDiscard(g_isDeclined))\n            {\n                Game_PlaySound(\"window_close\");\n\n                g_stateTime = ImGui::GetTime();\n                g_categoryTime = g_stateTime;\n\n                if (IsRestartRequired())\n                {\n                    s_state = OptionsMenuState::Restarting;\n                }\n                else\n                {\n                    Close();\n                }\n            }\n\n            break;\n        }\n\n        case OptionsMenuFlowState::OptionCursor:\n        {\n            MoveCursor(g_optionIndex, g_flowStateTime, 0, g_optionCount, []()\n            {\n                g_cursorArrowsTime = ImGui::GetTime();\n                g_scrollArrowsTime = g_cursorArrowsTime;\n            });\n\n            if (CheckAndDiscard(g_isDeclined))\n            {\n                Game_PlaySound(\"window_close\");\n\n                SetFlowState(OptionsMenuFlowState::CategoryCursor);\n\n                g_cursorArrowsTime = ImGui::GetTime();\n\n                s_commonMenu.SetDescription(GetCategoryDescription((OptionsMenuCategory)g_categoryIndex));\n            }\n\n            break;\n        }\n    }\n\n    DrawCategories(min, max);\n    DrawContainer(min, max);\n\n    s_commonMenu.Draw();\n\n    g_up = false;\n    g_upWasHeld = upIsHeld;\n    g_down = false;\n    g_downWasHeld = downIsHeld;\n    g_left = false;\n    g_leftWasHeld = leftIsHeld;\n    g_right = false;\n    g_rightWasHeld = rightIsHeld;\n    g_isAccepted = false;\n    g_isDeclined = false;\n    g_isReset = false;\n}\n\nvoid OptionsMenu::Init()\n{\n    g_upTexMainMenu9 = LOAD_ZSTD_TEXTURE(g_main_menu9);\n}\n\nvoid OptionsMenu::Open(bool isPause)\n{\n    s_commonMenu = CommonMenu(Localise(\"Options_Header_Name\"), \"\", isPause);\n    s_commonMenu.ReduceDraw = !isPause;\n\n    g_stateTime = ImGui::GetTime();\n    g_chevronTime = g_stateTime;\n    g_categoryTime = g_stateTime;\n    g_cursorArrowsTime = g_stateTime;\n\n    s_state = OptionsMenuState::Opening;\n    s_isVisible = true;\n    s_isPause = isPause;\n\n    // Update stored values for config definitions\n    // to check if their values have changed later.\n    for (auto& def : g_configDefinitions)\n        def->UpdateStore();\n\n    ResetSelection();\n\n    ButtonWindow::Open(\"Button_SelectBack\", s_isPause);\n    MainMenuTaskPatches::s_hideButtonWindow = true;\n}\n\nvoid OptionsMenu::Close()\n{\n    if (s_state == OptionsMenuState::Closing)\n        return;\n\n    s_state = OptionsMenuState::Closing;\n    g_stateTime = ImGui::GetTime();\n    g_cursorArrowsTime = g_stateTime;\n\n    if (s_pBgmCue)\n    {\n        s_pBgmCue->SetPause(true);\n        s_pBgmCue = nullptr;\n    }\n\n    if (s_isPause)\n        ButtonWindow::Close();\n\n    Config::Save();\n}\n\nbool OptionsMenu::CanClose()\n{\n    return OptionsMenu::s_flowState == OptionsMenuFlowState::CategoryCursor;\n}\n\nbool OptionsMenu::IsRestartRequired()\n{\n    if (!s_isVisible)\n        return false;\n\n    for (auto& def : g_configDefinitions)\n    {\n        if (def->RequiresRestart() && def->IsValueChanged())\n            return true;\n    }\n\n    return false;\n}\n\nvoid OptionsMenu::SetFlowState(OptionsMenuFlowState flowState)\n{\n    s_flowState = flowState;\n    g_flowStateTime = ImGui::GetTime();\n}\n"
  },
  {
    "path": "MarathonRecomp/ui/options_menu.h",
    "content": "#pragma once\n\n#include <api/Marathon.h>\n#include <ui/common_menu.h>\n\n#define MARATHON_RECOMP_OPTIONS_MENU\n\nenum class OptionsMenuState\n{\n    Opening,\n    Idle,\n    Closing,\n    Restarting\n};\n\nenum class OptionsMenuFlowState\n{\n    CategoryCursor,\n    OptionCursor,\n    OptionSelected\n};\n\nenum class OptionsMenuCategory\n{\n    System,\n    Input,\n    Audio,\n    Video,\n    Debug,\n    Count\n};\n\nclass OptionsMenu\n{\npublic:\n    static inline CommonMenu s_commonMenu{};\n    static inline OptionsMenuState s_state{};\n    static inline OptionsMenuFlowState s_flowState{};\n    static inline Sonicteam::MainMenuTask* s_pMainMenuTask{};\n    static inline Sonicteam::SoX::Audio::Cue* s_pBgmCue{};\n    static inline bool s_isVisible{};\n    static inline bool s_isPause{};\n    static inline bool s_isDebugUnlocked{};\n\n    static void Init();\n    static void Draw();\n    static void Open(bool isPause = false);\n    static void Close();\n    static bool CanClose();\n    static bool IsRestartRequired();\n    static void SetFlowState(OptionsMenuFlowState flowState);\n};\n"
  },
  {
    "path": "MarathonRecomp/user/achievement_data.cpp",
    "content": "#include \"achievement_data.h\"\n\n#define NUM_RECORDS sizeof(Records) / sizeof(AchRecord)\n\nbool AchievementData::VerifySignature() const\n{\n    char sig[4] = ACH_SIGNATURE;\n\n    return memcmp(Signature, sig, sizeof(Signature)) == 0;\n}\n\nbool AchievementData::VerifyVersion() const\n{\n    return Version <= ACH_VERSION;\n}\n\nbool AchievementData::VerifyChecksum()\n{\n    return Checksum == CalculateChecksum();\n}\n\nuint32_t AchievementData::CalculateChecksum()\n{\n    auto result = 0;\n\n    for (int i = 0; i < NUM_RECORDS; i++)\n    {\n        auto& record = Records[i];\n\n        for (size_t j = 0; j < sizeof(AchRecord); j++)\n            result ^= ((uint8_t*)(&record))[j];\n    }\n\n    return result;\n}\n"
  },
  {
    "path": "MarathonRecomp/user/achievement_data.h",
    "content": "#pragma once\n\n#include <user/paths.h>\n\n#define ACH_FILENAME  \"SonicNextAchievementData.bin\"\n#define ACH_SIGNATURE { 'A', 'C', 'H', ' ' }\n#define ACH_VERSION   1\n#define ACH_RECORDS   23\n\nclass AchievementData\n{\npublic:\n#pragma pack(push, 1)\n    struct AchRecord\n    {\n        uint16_t ID;\n        time_t Timestamp;\n        uint16_t Reserved[3];\n    };\n#pragma pack(pop)\n\n    char Signature[4] ACH_SIGNATURE;\n    uint32_t Version{ ACH_VERSION };\n    uint32_t Checksum{};\n    uint32_t Reserved{};\n    AchRecord Records[ACH_RECORDS]{};\n\n    bool VerifySignature() const;\n    bool VerifyVersion() const;\n    bool VerifyChecksum();\n    uint32_t CalculateChecksum();\n};\n"
  },
  {
    "path": "MarathonRecomp/user/achievement_manager.cpp",
    "content": "#include \"achievement_manager.h\"\n#include <os/logger.h>\n#include <ui/achievement_overlay.h>\n#include <user/config.h>\n\n#define NUM_RECORDS sizeof(AchievementManager::Data.Records) / sizeof(AchievementData::AchRecord)\n\ntime_t AchievementManager::GetTimestamp(uint16_t id)\n{\n    for (int i = 0; i < NUM_RECORDS; i++)\n    {\n        if (!Data.Records[i].ID)\n            break;\n\n        if (Data.Records[i].ID == id)\n            return Data.Records[i].Timestamp;\n    }\n\n    return 0;\n}\n\nsize_t AchievementManager::GetTotalRecords()\n{\n    auto result = 0;\n\n    for (int i = 0; i < NUM_RECORDS; i++)\n    {\n        if (!Data.Records[i].ID)\n            break;\n\n        result++;\n    }\n\n    return result;\n}\n\nbool AchievementManager::IsUnlocked(uint16_t id)\n{\n    for (int i = 0; i < NUM_RECORDS; i++)\n    {\n        if (!Data.Records[i].ID)\n            break;\n\n        if (Data.Records[i].ID == id)\n            return true;\n    }\n\n    return false;\n}\n\nvoid AchievementManager::Unlock(uint16_t id)\n{\n    if (IsUnlocked(id))\n        return;\n\n    for (int i = 0; i < NUM_RECORDS; i++)\n    {\n        if (Data.Records[i].ID == 0)\n        {\n            Data.Records[i].ID = id;\n            Data.Records[i].Timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());\n            break;\n        }\n    }\n\n    if (Config::AchievementNotifications)\n        AchievementOverlay::Open(id);\n}\n\nvoid AchievementManager::UnlockAll()\n{\n    for (uint16_t i = 1; i <= 23; i++)\n        AchievementManager::Unlock(i);\n}\n\nvoid AchievementManager::Reset()\n{\n    Data = {};\n}\n\nbool AchievementManager::LoadBinary()\n{\n    AchievementManager::Reset();\n\n    BinStatus = EAchBinStatus::Success;\n\n    auto dataPath = GetDataPath(true);\n\n    if (!std::filesystem::exists(dataPath))\n    {\n        // Try loading base achievement data as fallback.\n        dataPath = GetDataPath(false);\n\n        if (!std::filesystem::exists(dataPath))\n            return true;\n    }\n\n    std::error_code ec;\n    auto fileSize = std::filesystem::file_size(dataPath, ec);\n    auto dataSize = sizeof(AchievementData);\n\n    if (fileSize != dataSize)\n    {\n        BinStatus = EAchBinStatus::BadFileSize;\n        return false;\n    }\n\n    std::ifstream file(dataPath, std::ios::binary);\n\n    if (!file)\n    {\n        BinStatus = EAchBinStatus::IOError;\n        return false;\n    }\n\n    AchievementData data{};\n\n    file.read((char*)&data.Signature, sizeof(data.Signature));\n\n    if (!data.VerifySignature())\n    {\n        BinStatus = EAchBinStatus::BadSignature;\n        file.close();\n        return false;\n    }\n\n    file.read((char*)&data.Version, sizeof(data.Version));\n\n    if (!data.VerifyVersion())\n    {\n        BinStatus = EAchBinStatus::BadVersion;\n        file.close();\n        return false;\n    }\n\n    file.seekg(0);\n    file.read((char*)&data, sizeof(data));\n\n    if (!data.VerifyChecksum())\n    {\n        BinStatus = EAchBinStatus::BadChecksum;\n        file.close();\n        return false;\n    }\n\n    file.close();\n\n    memcpy(&Data, &data, dataSize);\n\n    return true;\n}\n\nbool AchievementManager::SaveBinary(bool ignoreStatus)\n{\n    if (!ignoreStatus && BinStatus != EAchBinStatus::Success)\n    {\n        LOGN_WARNING(\"Achievement data will not be saved in this session!\");\n        return false;\n    }\n\n    LOGN(\"Saving achievements...\");\n\n    std::ofstream file(GetDataPath(true), std::ios::binary);\n\n    if (!file)\n    {\n        LOGN_ERROR(\"Failed to write achievement data.\");\n        return false;\n    }\n\n    Data.Checksum = Data.CalculateChecksum();\n\n    file.write((const char*)&Data, sizeof(AchievementData));\n    file.close();\n\n    BinStatus = EAchBinStatus::Success;\n\n    return true;\n}\n"
  },
  {
    "path": "MarathonRecomp/user/achievement_manager.h",
    "content": "#pragma once\n\n#include <user/achievement_data.h>\n\nenum class EAchBinStatus\n{\n    Success,\n    IOError,\n    BadFileSize,\n    BadSignature,\n    BadVersion,\n    BadChecksum\n};\n\nclass AchievementManager\n{\npublic:\n    static inline AchievementData Data{};\n    static inline EAchBinStatus BinStatus{ EAchBinStatus::Success };\n\n    static std::filesystem::path GetDataPath(bool checkForMods)\n    {\n        return GetSavePath(checkForMods) / ACH_FILENAME;\n    }\n\n    static time_t GetTimestamp(uint16_t id);\n    static size_t GetTotalRecords();\n    static bool IsUnlocked(uint16_t id);\n    static void Unlock(uint16_t id);\n    static void UnlockAll();\n    static void Reset();\n    static bool LoadBinary();\n    static bool SaveBinary(bool ignoreStatus = false);\n};\n"
  },
  {
    "path": "MarathonRecomp/user/config.cpp",
    "content": "#include \"config.h\"\n#include <hid/hid.h>\n#include <os/logger.h>\n#include <ui/game_window.h>\n#include <ui/options_menu.h>\n#include <user/paths.h>\n#include <app.h>\n\nstd::vector<IConfigDef*> g_configDefinitions;\n\n#define CONFIG_DEFINE_ENUM_TEMPLATE(type) \\\n    static std::unordered_map<std::string, type> g_##type##_template =\n\nCONFIG_DEFINE_ENUM_TEMPLATE(ELanguage)\n{\n    { \"English\",  ELanguage::English },\n    { \"Japanese\", ELanguage::Japanese },\n    { \"German\",   ELanguage::German },\n    { \"French\",   ELanguage::French },\n    { \"Spanish\",  ELanguage::Spanish },\n    { \"Italian\",  ELanguage::Italian }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(ECameraRotationMode)\n{\n    { \"Normal\",  ECameraRotationMode::Normal },\n    { \"Reverse\", ECameraRotationMode::Reverse }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EControllerIcons)\n{\n    { \"Auto\",        EControllerIcons::Auto },\n    { \"Xbox\",        EControllerIcons::Xbox },\n    { \"PlayStation\", EControllerIcons::PlayStation }\n};\nCONFIG_DEFINE_ENUM_TEMPLATE(ELightDash)\n{\n    { \"X\", ELightDash::X },\n    { \"Y\", ELightDash::Y }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(ESlidingAttack)\n{\n    { \"B\", ESlidingAttack::B },\n    { \"X\", ESlidingAttack::X }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(SDL_Scancode)\n{\n    { \"???\", SDL_SCANCODE_UNKNOWN },\n    { \"A\", SDL_SCANCODE_A },\n    { \"B\", SDL_SCANCODE_B },\n    { \"C\", SDL_SCANCODE_C },\n    { \"D\", SDL_SCANCODE_D },\n    { \"E\", SDL_SCANCODE_E },\n    { \"F\", SDL_SCANCODE_F },\n    { \"G\", SDL_SCANCODE_G },\n    { \"H\", SDL_SCANCODE_H },\n    { \"I\", SDL_SCANCODE_I },\n    { \"J\", SDL_SCANCODE_J },\n    { \"K\", SDL_SCANCODE_K },\n    { \"L\", SDL_SCANCODE_L },\n    { \"M\", SDL_SCANCODE_M },\n    { \"N\", SDL_SCANCODE_N },\n    { \"O\", SDL_SCANCODE_O },\n    { \"P\", SDL_SCANCODE_P },\n    { \"Q\", SDL_SCANCODE_Q },\n    { \"R\", SDL_SCANCODE_R },\n    { \"S\", SDL_SCANCODE_S },\n    { \"T\", SDL_SCANCODE_T },\n    { \"U\", SDL_SCANCODE_U },\n    { \"V\", SDL_SCANCODE_V },\n    { \"W\", SDL_SCANCODE_W },\n    { \"X\", SDL_SCANCODE_X },\n    { \"Y\", SDL_SCANCODE_Y },\n    { \"Z\", SDL_SCANCODE_Z },\n    { \"1\", SDL_SCANCODE_1 },\n    { \"2\", SDL_SCANCODE_2 },\n    { \"3\", SDL_SCANCODE_3 },\n    { \"4\", SDL_SCANCODE_4 },\n    { \"5\", SDL_SCANCODE_5 },\n    { \"6\", SDL_SCANCODE_6 },\n    { \"7\", SDL_SCANCODE_7 },\n    { \"8\", SDL_SCANCODE_8 },\n    { \"9\", SDL_SCANCODE_9 },\n    { \"0\", SDL_SCANCODE_0 },\n    { \"RETURN\", SDL_SCANCODE_RETURN },\n    { \"ESCAPE\", SDL_SCANCODE_ESCAPE },\n    { \"BACKSPACE\", SDL_SCANCODE_BACKSPACE },\n    { \"TAB\", SDL_SCANCODE_TAB },\n    { \"SPACE\", SDL_SCANCODE_SPACE },\n    { \"MINUS\", SDL_SCANCODE_MINUS },\n    { \"EQUALS\", SDL_SCANCODE_EQUALS },\n    { \"LEFT BRACKET\", SDL_SCANCODE_LEFTBRACKET },\n    { \"RIGHT BRACKET\", SDL_SCANCODE_RIGHTBRACKET },\n    { \"BACKSLASH\", SDL_SCANCODE_BACKSLASH },\n    { \"NON-US HASH\", SDL_SCANCODE_NONUSHASH },\n    { \"SEMICOLON\", SDL_SCANCODE_SEMICOLON },\n    { \"APOSTROPHE\", SDL_SCANCODE_APOSTROPHE },\n    { \"GRAVE\", SDL_SCANCODE_GRAVE },\n    { \"COMMA\", SDL_SCANCODE_COMMA },\n    { \"PERIOD\", SDL_SCANCODE_PERIOD },\n    { \"SLASH\", SDL_SCANCODE_SLASH },\n    { \"CAPS LOCK\", SDL_SCANCODE_CAPSLOCK },\n    { \"F1\", SDL_SCANCODE_F1 },\n    { \"F2\", SDL_SCANCODE_F2 },\n    { \"F3\", SDL_SCANCODE_F3 },\n    { \"F4\", SDL_SCANCODE_F4 },\n    { \"F5\", SDL_SCANCODE_F5 },\n    { \"F6\", SDL_SCANCODE_F6 },\n    { \"F7\", SDL_SCANCODE_F7 },\n    { \"F8\", SDL_SCANCODE_F8 },\n    { \"F9\", SDL_SCANCODE_F9 },\n    { \"F10\", SDL_SCANCODE_F10 },\n    { \"F11\", SDL_SCANCODE_F11 },\n    { \"F12\", SDL_SCANCODE_F12 },\n    { \"PRINT SCREEN\", SDL_SCANCODE_PRINTSCREEN },\n    { \"SCROLL LOCK\", SDL_SCANCODE_SCROLLLOCK },\n    { \"PAUSE\", SDL_SCANCODE_PAUSE },\n    { \"INSERT\", SDL_SCANCODE_INSERT },\n    { \"HOME\", SDL_SCANCODE_HOME },\n    { \"PAGE UP\", SDL_SCANCODE_PAGEUP },\n    { \"DELETE\", SDL_SCANCODE_DELETE },\n    { \"END\", SDL_SCANCODE_END },\n    { \"PAGE DOWN\", SDL_SCANCODE_PAGEDOWN },\n    { \"RIGHT\", SDL_SCANCODE_RIGHT },\n    { \"LEFT\", SDL_SCANCODE_LEFT },\n    { \"DOWN\", SDL_SCANCODE_DOWN },\n    { \"UP\", SDL_SCANCODE_UP },\n    { \"NUM LOCK\", SDL_SCANCODE_NUMLOCKCLEAR },\n    { \"KP DIVIDE\", SDL_SCANCODE_KP_DIVIDE },\n    { \"KP MULTIPLY\", SDL_SCANCODE_KP_MULTIPLY },\n    { \"KP MINUS\", SDL_SCANCODE_KP_MINUS },\n    { \"KP PLUS\", SDL_SCANCODE_KP_PLUS },\n    { \"KP ENTER\", SDL_SCANCODE_KP_ENTER },\n    { \"KP 1\", SDL_SCANCODE_KP_1 },\n    { \"KP 2\", SDL_SCANCODE_KP_2 },\n    { \"KP 3\", SDL_SCANCODE_KP_3 },\n    { \"KP 4\", SDL_SCANCODE_KP_4 },\n    { \"KP 5\", SDL_SCANCODE_KP_5 },\n    { \"KP 6\", SDL_SCANCODE_KP_6 },\n    { \"KP 7\", SDL_SCANCODE_KP_7 },\n    { \"KP 8\", SDL_SCANCODE_KP_8 },\n    { \"KP 9\", SDL_SCANCODE_KP_9 },\n    { \"KP 0\", SDL_SCANCODE_KP_0 },\n    { \"KP PERIOD\", SDL_SCANCODE_KP_PERIOD },\n    { \"NON-US BACKSLASH\", SDL_SCANCODE_NONUSBACKSLASH },\n    { \"APPLICATION\", SDL_SCANCODE_APPLICATION },\n    { \"POWER\", SDL_SCANCODE_POWER },\n    { \"KP EQUALS\", SDL_SCANCODE_KP_EQUALS },\n    { \"F13\", SDL_SCANCODE_F13 },\n    { \"F14\", SDL_SCANCODE_F14 },\n    { \"F15\", SDL_SCANCODE_F15 },\n    { \"F16\", SDL_SCANCODE_F16 },\n    { \"F17\", SDL_SCANCODE_F17 },\n    { \"F18\", SDL_SCANCODE_F18 },\n    { \"F19\", SDL_SCANCODE_F19 },\n    { \"F20\", SDL_SCANCODE_F20 },\n    { \"F21\", SDL_SCANCODE_F21 },\n    { \"F22\", SDL_SCANCODE_F22 },\n    { \"F23\", SDL_SCANCODE_F23 },\n    { \"F24\", SDL_SCANCODE_F24 },\n    { \"EXECUTE\", SDL_SCANCODE_EXECUTE },\n    { \"HELP\", SDL_SCANCODE_HELP },\n    { \"MENU\", SDL_SCANCODE_MENU },\n    { \"SELECT\", SDL_SCANCODE_SELECT },\n    { \"STOP\", SDL_SCANCODE_STOP },\n    { \"AGAIN\", SDL_SCANCODE_AGAIN },\n    { \"UNDO\", SDL_SCANCODE_UNDO },\n    { \"CUT\", SDL_SCANCODE_CUT },\n    { \"COPY\", SDL_SCANCODE_COPY },\n    { \"PASTE\", SDL_SCANCODE_PASTE },\n    { \"FIND\", SDL_SCANCODE_FIND },\n    { \"MUTE\", SDL_SCANCODE_MUTE },\n    { \"VOLUME UP\", SDL_SCANCODE_VOLUMEUP },\n    { \"VOLUME DOWN\", SDL_SCANCODE_VOLUMEDOWN },\n    { \"KP COMMA\", SDL_SCANCODE_KP_COMMA },\n    { \"KP EQUALS AS400\", SDL_SCANCODE_KP_EQUALSAS400 },\n    { \"INTERNATIONAL 1\", SDL_SCANCODE_INTERNATIONAL1 },\n    { \"INTERNATIONAL 2\", SDL_SCANCODE_INTERNATIONAL2 },\n    { \"INTERNATIONAL 3\", SDL_SCANCODE_INTERNATIONAL3 },\n    { \"INTERNATIONAL 4\", SDL_SCANCODE_INTERNATIONAL4 },\n    { \"INTERNATIONAL 5\", SDL_SCANCODE_INTERNATIONAL5 },\n    { \"INTERNATIONAL 6\", SDL_SCANCODE_INTERNATIONAL6 },\n    { \"INTERNATIONAL 7\", SDL_SCANCODE_INTERNATIONAL7 },\n    { \"INTERNATIONAL 8\", SDL_SCANCODE_INTERNATIONAL8 },\n    { \"INTERNATIONAL 9\", SDL_SCANCODE_INTERNATIONAL9 },\n    { \"LANG 1\", SDL_SCANCODE_LANG1 },\n    { \"LANG 2\", SDL_SCANCODE_LANG2 },\n    { \"LANG 3\", SDL_SCANCODE_LANG3 },\n    { \"LANG 4\", SDL_SCANCODE_LANG4 },\n    { \"LANG 5\", SDL_SCANCODE_LANG5 },\n    { \"LANG 6\", SDL_SCANCODE_LANG6 },\n    { \"LANG 7\", SDL_SCANCODE_LANG7 },\n    { \"LANG 8\", SDL_SCANCODE_LANG8 },\n    { \"LANG 9\", SDL_SCANCODE_LANG9 },\n    { \"ALT ERASE\", SDL_SCANCODE_ALTERASE },\n    { \"SYS REQ\", SDL_SCANCODE_SYSREQ },\n    { \"CANCEL\", SDL_SCANCODE_CANCEL },\n    { \"CLEAR\", SDL_SCANCODE_CLEAR },\n    { \"PRIOR\", SDL_SCANCODE_PRIOR },\n    { \"RETURN 2\", SDL_SCANCODE_RETURN2 },\n    { \"SEPARATOR\", SDL_SCANCODE_SEPARATOR },\n    { \"OUT\", SDL_SCANCODE_OUT },\n    { \"OPER\", SDL_SCANCODE_OPER },\n    { \"CLEAR AGAIN\", SDL_SCANCODE_CLEARAGAIN },\n    { \"CR SEL\", SDL_SCANCODE_CRSEL },\n    { \"EX SEL\", SDL_SCANCODE_EXSEL },\n    { \"KP 00\", SDL_SCANCODE_KP_00 },\n    { \"KP 000\", SDL_SCANCODE_KP_000 },\n    { \"THOUSANDS SEPARATOR\", SDL_SCANCODE_THOUSANDSSEPARATOR },\n    { \"DECIMAL SEPARATOR\", SDL_SCANCODE_DECIMALSEPARATOR },\n    { \"CURRENCY UNIT\", SDL_SCANCODE_CURRENCYUNIT },\n    { \"CURRENCY SUBUNIT\", SDL_SCANCODE_CURRENCYSUBUNIT },\n    { \"KP LEFT PAREN\", SDL_SCANCODE_KP_LEFTPAREN },\n    { \"KP RIGHT PAREN\", SDL_SCANCODE_KP_RIGHTPAREN },\n    { \"KP LEFT BRACE\", SDL_SCANCODE_KP_LEFTBRACE },\n    { \"KP RIGHT BRACE\", SDL_SCANCODE_KP_RIGHTBRACE },\n    { \"KP TAB\", SDL_SCANCODE_KP_TAB },\n    { \"KP BACKSPACE\", SDL_SCANCODE_KP_BACKSPACE },\n    { \"KP A\", SDL_SCANCODE_KP_A },\n    { \"KP B\", SDL_SCANCODE_KP_B },\n    { \"KP C\", SDL_SCANCODE_KP_C },\n    { \"KP D\", SDL_SCANCODE_KP_D },\n    { \"KP E\", SDL_SCANCODE_KP_E },\n    { \"KP F\", SDL_SCANCODE_KP_F },\n    { \"KP XOR\", SDL_SCANCODE_KP_XOR },\n    { \"KP POWER\", SDL_SCANCODE_KP_POWER },\n    { \"KP PERCENT\", SDL_SCANCODE_KP_PERCENT },\n    { \"KP LESS\", SDL_SCANCODE_KP_LESS },\n    { \"KP GREATER\", SDL_SCANCODE_KP_GREATER },\n    { \"KP AMPERSAND\", SDL_SCANCODE_KP_AMPERSAND },\n    { \"KP DBL AMPERSAND\", SDL_SCANCODE_KP_DBLAMPERSAND },\n    { \"KP VERTICAL BAR\", SDL_SCANCODE_KP_VERTICALBAR },\n    { \"KP DBL VERTICAL BAR\", SDL_SCANCODE_KP_DBLVERTICALBAR },\n    { \"KP COLON\", SDL_SCANCODE_KP_COLON },\n    { \"KP HASH\", SDL_SCANCODE_KP_HASH },\n    { \"KP SPACE\", SDL_SCANCODE_KP_SPACE },\n    { \"KP AT\", SDL_SCANCODE_KP_AT },\n    { \"KP EXCLAM\", SDL_SCANCODE_KP_EXCLAM },\n    { \"KP MEM STORE\", SDL_SCANCODE_KP_MEMSTORE },\n    { \"KP MEM RECALL\", SDL_SCANCODE_KP_MEMRECALL },\n    { \"KP MEM CLEAR\", SDL_SCANCODE_KP_MEMCLEAR },\n    { \"KP MEM ADD\", SDL_SCANCODE_KP_MEMADD },\n    { \"KP MEM SUBTRACT\", SDL_SCANCODE_KP_MEMSUBTRACT },\n    { \"KP MEM MULTIPLY\", SDL_SCANCODE_KP_MEMMULTIPLY },\n    { \"KP MEM DIVIDE\", SDL_SCANCODE_KP_MEMDIVIDE },\n    { \"KP PLUS/MINUS\", SDL_SCANCODE_KP_PLUSMINUS },\n    { \"KP CLEAR\", SDL_SCANCODE_KP_CLEAR },\n    { \"KP CLEAR ENTRY\", SDL_SCANCODE_KP_CLEARENTRY },\n    { \"KP BINARY\", SDL_SCANCODE_KP_BINARY },\n    { \"KP OCTAL\", SDL_SCANCODE_KP_OCTAL },\n    { \"KP DECIMAL\", SDL_SCANCODE_KP_DECIMAL },\n    { \"KP HEXADECIMAL\", SDL_SCANCODE_KP_HEXADECIMAL },\n    { \"LEFT CTRL\", SDL_SCANCODE_LCTRL },\n    { \"LEFT SHIFT\", SDL_SCANCODE_LSHIFT },\n    { \"LEFT ALT\", SDL_SCANCODE_LALT },\n    { \"LEFT SUPER\", SDL_SCANCODE_LGUI },\n    { \"RIGHT CTRL\", SDL_SCANCODE_RCTRL },\n    { \"RIGHT SHIFT\", SDL_SCANCODE_RSHIFT },\n    { \"RIGHT ALT\", SDL_SCANCODE_RALT },\n    { \"RIGHT SUPER\", SDL_SCANCODE_RGUI },\n    { \"MODE\", SDL_SCANCODE_MODE },\n    { \"AUDIO NEXT\", SDL_SCANCODE_AUDIONEXT },\n    { \"AUDIO PREV\", SDL_SCANCODE_AUDIOPREV },\n    { \"AUDIO STOP\", SDL_SCANCODE_AUDIOSTOP },\n    { \"AUDIO PLAY\", SDL_SCANCODE_AUDIOPLAY },\n    { \"AUDIO MUTE\", SDL_SCANCODE_AUDIOMUTE },\n    { \"MEDIA SELECT\", SDL_SCANCODE_MEDIASELECT },\n    { \"WWW\", SDL_SCANCODE_WWW },\n    { \"MAIL\", SDL_SCANCODE_MAIL },\n    { \"CALCULATOR\", SDL_SCANCODE_CALCULATOR },\n    { \"COMPUTER\", SDL_SCANCODE_COMPUTER },\n    { \"AC SEARCH\", SDL_SCANCODE_AC_SEARCH },\n    { \"AC HOME\", SDL_SCANCODE_AC_HOME },\n    { \"AC BACK\", SDL_SCANCODE_AC_BACK },\n    { \"AC FORWARD\", SDL_SCANCODE_AC_FORWARD },\n    { \"AC STOP\", SDL_SCANCODE_AC_STOP },\n    { \"AC REFRESH\", SDL_SCANCODE_AC_REFRESH },\n    { \"AC BOOKMARKS\", SDL_SCANCODE_AC_BOOKMARKS },\n    { \"BRIGHTNESS DOWN\", SDL_SCANCODE_BRIGHTNESSDOWN },\n    { \"BRIGHTNESS UP\", SDL_SCANCODE_BRIGHTNESSUP },\n    { \"DISPLAY SWITCH\", SDL_SCANCODE_DISPLAYSWITCH },\n    { \"KBD ILLUM TOGGLE\", SDL_SCANCODE_KBDILLUMTOGGLE },\n    { \"KBD ILLUM DOWN\", SDL_SCANCODE_KBDILLUMDOWN },\n    { \"KBD ILLUM UP\", SDL_SCANCODE_KBDILLUMUP },\n    { \"EJECT\", SDL_SCANCODE_EJECT },\n    { \"SLEEP\", SDL_SCANCODE_SLEEP },\n    { \"APP 1\", SDL_SCANCODE_APP1 },\n    { \"APP 2\", SDL_SCANCODE_APP2 },\n    { \"AUDIO REWIND\", SDL_SCANCODE_AUDIOREWIND },\n    { \"AUDIO FAST FORWARD\", SDL_SCANCODE_AUDIOFASTFORWARD },\n    { \"SOFT LEFT\", SDL_SCANCODE_SOFTLEFT },\n    { \"SOFT RIGHT\", SDL_SCANCODE_SOFTRIGHT },\n    { \"CALL\", SDL_SCANCODE_CALL },\n    { \"END CALL\", SDL_SCANCODE_ENDCALL },\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EChannelConfiguration)\n{\n    { \"Stereo\",   EChannelConfiguration::Stereo },\n    { \"Surround\", EChannelConfiguration::Surround }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EVoiceLanguage)\n{\n    { \"English\",  EVoiceLanguage::English },\n    { \"Japanese\", EVoiceLanguage::Japanese }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EGraphicsAPI)\n{\n    { \"Auto\", EGraphicsAPI::Auto },\n#ifdef MARATHON_RECOMP_D3D12\n    { \"D3D12\",  EGraphicsAPI::D3D12 },\n#endif\n#ifdef MARATHON_RECOMP_METAL\n    { \"Metal\",  EGraphicsAPI::Metal },\n#endif\n    { \"Vulkan\", EGraphicsAPI::Vulkan }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EWindowState)\n{\n    { \"Normal\",    EWindowState::Normal },\n    { \"Maximised\", EWindowState::Maximised },\n    { \"Maximized\", EWindowState::Maximised }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EAspectRatio)\n{\n    { \"Auto\",     EAspectRatio::Auto },\n    { \"Original\", EAspectRatio::Original }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(ETripleBuffering)\n{\n    { \"Auto\", ETripleBuffering::Auto },\n    { \"On\",   ETripleBuffering::On },\n    { \"Off\",  ETripleBuffering::Off }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EAntiAliasing)\n{\n    { \"Off\",    EAntiAliasing::Off },\n    { \"2x MSAA\", EAntiAliasing::MSAA2x },\n    { \"4x MSAA\", EAntiAliasing::MSAA4x },\n    { \"8x MSAA\", EAntiAliasing::MSAA8x }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EShadowResolution)\n{\n    { \"512\",      EShadowResolution::x512 },\n    { \"1024\",     EShadowResolution::x1024 },\n    { \"2048\",     EShadowResolution::x2048 },\n    { \"4096\",     EShadowResolution::x4096 },\n    { \"8192\",     EShadowResolution::x8192 },\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EReflectionResolution)\n{\n    { \"Full\",    EReflectionResolution::Full },\n    { \"Half\",    EReflectionResolution::Half },\n    { \"Quarter\", EReflectionResolution::Quarter },\n    { \"Eighth\",  EReflectionResolution::Eighth },\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(ERadialBlur)\n{\n    { \"Off\",      ERadialBlur::Off },\n    { \"Original\", ERadialBlur::Original },\n    { \"Enhanced\", ERadialBlur::Enhanced }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(ECutsceneAspectRatio)\n{\n    { \"Original\", ECutsceneAspectRatio::Original },\n    { \"Unlocked\", ECutsceneAspectRatio::Unlocked }\n};\n\nCONFIG_DEFINE_ENUM_TEMPLATE(EUIAlignmentMode)\n{\n    { \"Edge\",    EUIAlignmentMode::Edge },\n    { \"Centre\",  EUIAlignmentMode::Centre },\n    { \"Center\",  EUIAlignmentMode::Centre }\n};\n\n#undef  CONFIG_DEFINE\n#define CONFIG_DEFINE(section, type, name, defaultValue, requiresRestart) \\\n    ConfigDef<type> Config::name{section, #name, defaultValue, requiresRestart};\n\n#undef  CONFIG_DEFINE_HIDDEN\n#define CONFIG_DEFINE_HIDDEN(section, type, name, defaultValue, requiresRestart) \\\n    ConfigDef<type, true> Config::name{section, #name, defaultValue, requiresRestart};\n\n#undef  CONFIG_DEFINE_LOCALISED\n#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue, requiresRestart) \\\n    extern CONFIG_LOCALE g_##name##_locale; \\\n    ConfigDef<type> Config::name{section, #name, &g_##name##_locale, defaultValue, requiresRestart};\n\n#undef  CONFIG_DEFINE_ENUM\n#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue, requiresRestart) \\\n    ConfigDef<type> Config::name{section, #name, defaultValue, requiresRestart, &g_##type##_template};\n\n#undef  CONFIG_DEFINE_ENUM_LOCALISED\n#define CONFIG_DEFINE_ENUM_LOCALISED(section, type, name, defaultValue, requiresRestart) \\\n    extern CONFIG_LOCALE g_##name##_locale; \\\n    extern CONFIG_ENUM_LOCALE(type) g_##type##_locale; \\\n    ConfigDef<type> Config::name{section, #name, &g_##name##_locale, defaultValue, requiresRestart, &g_##type##_template, &g_##type##_locale};\n\n#include \"config_def.h\"\n\n// CONFIG_DEFINE\ntemplate<typename T, bool isHidden>\nConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, T defaultValue, bool requiresRestart) : Section(section), Name(name), DefaultValue(defaultValue), IsRestartRequired(requiresRestart)\n{\n    g_configDefinitions.emplace_back(this);\n}\n\n// CONFIG_DEFINE_LOCALISED\ntemplate<typename T, bool isHidden>\nConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, bool requiresRestart) : Section(section), Name(name), Locale(nameLocale), DefaultValue(defaultValue), IsRestartRequired(requiresRestart)\n{\n    g_configDefinitions.emplace_back(this);\n}\n\n// CONFIG_DEFINE_ENUM\ntemplate<typename T, bool isHidden>\nConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, T defaultValue, bool requiresRestart, std::unordered_map<std::string, T>* enumTemplate) : Section(section), Name(name), DefaultValue(defaultValue), IsRestartRequired(requiresRestart), EnumTemplate(enumTemplate)\n{\n    for (const auto& pair : *EnumTemplate)\n        EnumTemplateReverse[pair.second] = pair.first;\n\n    g_configDefinitions.emplace_back(this);\n}\n\n// CONFIG_DEFINE_ENUM_LOCALISED\ntemplate<typename T, bool isHidden>\nConfigDef<T, isHidden>::ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, bool requiresRestart, std::unordered_map<std::string, T>* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale) : Section(section), Name(name), Locale(nameLocale), DefaultValue(defaultValue), IsRestartRequired(requiresRestart), EnumTemplate(enumTemplate), EnumLocale(enumLocale)\n{\n    for (const auto& pair : *EnumTemplate)\n        EnumTemplateReverse[pair.second] = pair.first;\n\n    g_configDefinitions.emplace_back(this);\n}\n\ntemplate<typename T, bool isHidden>\nConfigDef<T, isHidden>::~ConfigDef() = default;\n\ntemplate<typename T, bool isHidden>\nbool ConfigDef<T, isHidden>::IsHidden()\n{\n    return isHidden && !IsLoadedFromConfig;\n}\n\ntemplate<typename T, bool isHidden>\nvoid ConfigDef<T, isHidden>::SetHidden(bool hidden)\n{\n    IsLoadedFromConfig = !hidden;\n}\n\ntemplate<typename T, bool isHidden>\nvoid ConfigDef<T, isHidden>::ReadValue(toml::v3::ex::parse_result& toml)\n{\n    if (auto pSection = toml[Section].as_table())\n    {\n        const auto& section = *pSection;\n\n        if constexpr (std::is_same<T, std::string>::value)\n        {\n            Value = section[Name].value_or(DefaultValue);\n        }\n        else if constexpr (std::is_enum_v<T>)\n        {\n            auto value = section[Name].value_or(std::string());\n            auto it = EnumTemplate->find(value);\n\n            if (it != EnumTemplate->end())\n            {\n                Value = it->second;\n            }\n            else\n            {\n                Value = DefaultValue;\n            }\n        }\n        else\n        {\n            Value = section[Name].value_or(DefaultValue);\n        }\n\n        if (Callback)\n            Callback(this);\n\n        if (pSection->contains(Name))\n            IsLoadedFromConfig = true;\n    }\n}\n\ntemplate<typename T, bool isHidden>\nvoid ConfigDef<T, isHidden>::MakeDefault()\n{\n    Value = DefaultValue;\n\n    if constexpr (std::is_enum_v<T>)\n        SnapToNearestAccessibleValue(false);\n}\n\ntemplate<typename T, bool isHidden>\nstd::string_view ConfigDef<T, isHidden>::GetSection() const\n{\n    return Section;\n}\n\ntemplate<typename T, bool isHidden>\nstd::string_view ConfigDef<T, isHidden>::GetName() const\n{\n    return Name;\n}\n\ntemplate<typename T, bool isHidden>\nstd::string ConfigDef<T, isHidden>::GetNameLocalised(ELanguage language) const\n{\n    if (Locale != nullptr)\n    {\n        auto languageFindResult = Locale->find(language);\n        if (languageFindResult == Locale->end())\n            languageFindResult = Locale->find(ELanguage::English);\n\n        if (languageFindResult != Locale->end())\n            return std::get<0>(languageFindResult->second);\n    }\n\n    return Name;\n}\n\ntemplate<typename T, bool isHidden>\nstd::string ConfigDef<T, isHidden>::GetDescription(ELanguage language) const\n{\n    if (Locale != nullptr)\n    {\n        auto languageFindResult = Locale->find(language);\n        if (languageFindResult == Locale->end())\n            languageFindResult = Locale->find(ELanguage::English);\n\n        if (languageFindResult != Locale->end())\n            return std::get<1>(languageFindResult->second);\n    }\n\n    return \"\";\n}\n\ntemplate<typename T, bool isHidden>\nbool ConfigDef<T, isHidden>::IsDefaultValue() const\n{\n    return Value == DefaultValue;\n}\n\ntemplate<typename T, bool isHidden>\nconst void* ConfigDef<T, isHidden>::GetValue() const\n{\n    return &Value;\n}\n\ntemplate<typename T, bool isHidden>\nstd::string ConfigDef<T, isHidden>::GetValueLocalised(ELanguage language) const\n{\n    CONFIG_ENUM_LOCALE(T)* locale = nullptr;\n\n    if constexpr (std::is_enum_v<T>)\n    {\n        locale = EnumLocale;\n    }\n    else if constexpr (std::is_same_v<T, bool>)\n    {\n        return Value\n            ? Localise(\"Common_On\")\n            : Localise(\"Common_Off\");\n    }\n\n    if (locale != nullptr)\n    {\n        ELanguage languages[] = { language, ELanguage::English };\n\n        for (auto languageToFind : languages)\n        {\n            auto languageFindResult = locale->find(languageToFind);\n\n            if (languageFindResult != locale->end())\n            {\n                auto valueFindResult = languageFindResult->second.find(Value);\n                if (valueFindResult != languageFindResult->second.end())\n                    return std::get<0>(valueFindResult->second);\n            }\n\n            if (languageToFind == ELanguage::English)\n                break;\n        }\n    }\n\n    return ToString(false);\n}\n\ntemplate<typename T, bool isHidden>\nstd::string ConfigDef<T, isHidden>::GetValueDescription(ELanguage language) const\n{\n    CONFIG_ENUM_LOCALE(T)* locale = nullptr;\n\n    if constexpr (std::is_enum_v<T>)\n    {\n        locale = EnumLocale;\n    }\n    else if constexpr (std::is_same_v<T, bool>)\n    {\n        return \"\";\n    }\n\n    if (locale != nullptr)\n    {\n        ELanguage languages[] = { language, ELanguage::English };\n\n        for (auto languageToFind : languages)\n        {\n            auto languageFindResult = locale->find(languageToFind);\n\n            if (languageFindResult != locale->end())\n            {\n                auto valueFindResult = languageFindResult->second.find(Value);\n                if (valueFindResult != languageFindResult->second.end())\n                    return std::get<1>(valueFindResult->second);\n            }\n\n            if (languageToFind == ELanguage::English)\n                break;\n        }\n    }\n\n    return \"\";\n}\n\ntemplate<typename T, bool isHidden>\nstd::string ConfigDef<T, isHidden>::GetDefinition(bool withSection) const\n{\n    std::string result;\n\n    if (withSection)\n        result += \"[\" + Section + \"]\\n\";\n\n    result += Name + \" = \" + ToString();\n\n    return result;\n}\n\ntemplate<typename T, bool isHidden>\nstd::string ConfigDef<T, isHidden>::ToString(bool strWithQuotes) const\n{\n    std::string result = \"N/A\";\n\n    if constexpr (std::is_same_v<T, std::string>)\n    {\n        result = fmt::format(\"{}\", Value);\n\n        if (strWithQuotes)\n            result = fmt::format(\"\\\"{}\\\"\", result);\n    }\n    else if constexpr (std::is_enum_v<T>)\n    {\n        auto it = EnumTemplateReverse.find(Value);\n\n        if (it != EnumTemplateReverse.end())\n            result = fmt::format(\"{}\", it->second);\n\n        if (strWithQuotes)\n            result = fmt::format(\"\\\"{}\\\"\", result);\n    }\n    else\n    {\n        result = fmt::format(\"{}\", Value);\n    }\n\n    return result;\n}\n\ntemplate<typename T, bool isHidden>\nvoid ConfigDef<T, isHidden>::GetLocaleStrings(std::vector<std::string_view>& localeStrings) const\n{\n    if (Locale != nullptr)\n    {\n        for (auto& [language, nameAndDesc] : *Locale)\n        {\n            localeStrings.push_back(std::get<0>(nameAndDesc));\n            localeStrings.push_back(std::get<1>(nameAndDesc));\n        }\n    }\n\n    if (EnumLocale != nullptr)\n    {\n        for (auto& [language, locale] : *EnumLocale)\n        {\n            for (auto& [value, nameAndDesc] : locale)\n            {\n                localeStrings.push_back(std::get<0>(nameAndDesc));\n                localeStrings.push_back(std::get<1>(nameAndDesc));\n            }\n        }\n    }\n}\n\ntemplate<typename T, bool isHidden>\nvoid ConfigDef<T, isHidden>::SnapToNearestAccessibleValue(bool searchUp)\n{\n    if constexpr (std::is_enum_v<T>)\n    {\n        if (EnumTemplateReverse.empty() || InaccessibleValues.empty())\n            return;\n\n        if (EnumTemplateReverse.size() == InaccessibleValues.size())\n        {\n            assert(false && \"All enum values are marked inaccessible and the nearest accessible value cannot be determined.\");\n            return;\n        }\n\n        auto it = EnumTemplateReverse.find(Value);\n\n        if (it == EnumTemplateReverse.end())\n        {\n            assert(false && \"Enum value does not exist in the template.\");\n            return;\n        }\n\n        // Skip the enum value if it's marked as inaccessible.\n        while (InaccessibleValues.find(it->first) != InaccessibleValues.end())\n        {\n            if (searchUp)\n            {\n                ++it;\n\n                if (it == EnumTemplateReverse.end())\n                    it = EnumTemplateReverse.begin();\n            }\n            else\n            {\n                if (it == EnumTemplateReverse.begin())\n                    it = EnumTemplateReverse.end();\n\n                --it;\n            }\n        }\n\n        Value = it->first;\n    }\n}\n\ntemplate<typename T, bool isHidden>\nbool ConfigDef<T, isHidden>::RequiresRestart()\n{\n    return IsRestartRequired;\n}\n\ntemplate<typename T, bool isHidden>\nvoid ConfigDef<T, isHidden>::UpdateStore()\n{\n    m_storedValue = Value;\n}\n\ntemplate<typename T, bool isHidden>\nbool ConfigDef<T, isHidden>::IsValueChanged()\n{\n    return m_storedValue != Value;\n}\n\nstd::filesystem::path Config::GetConfigPath()\n{\n    return GetUserPath() / \"config.toml\";\n}\n\nvoid Config::CreateCallbacks()\n{\n    Config::Language.Callback = [](ConfigDef<ELanguage>* def)\n    {\n        if (!App::s_isInit)\n            return;\n\n        OptionsMenu::s_commonMenu.SetTitle(Localise(\"Options_Header_Name\"), false);\n    };\n\n    Config::WindowSize.LockCallback = [](ConfigDef<int32_t>* def)\n    {\n        // Try matching the current window size with a known configuration.\n        if (def->Value < 0)\n            def->Value = GameWindow::FindNearestDisplayMode();\n    };\n\n    Config::WindowSize.ApplyCallback = [](ConfigDef<int32_t>* def)\n    {\n        auto displayModes = GameWindow::GetDisplayModes();\n\n        // Use largest supported resolution if overflowed.\n        if (def->Value >= displayModes.size())\n            def->Value = displayModes.size() - 1;\n\n        auto& mode = displayModes[def->Value];\n        auto centre = SDL_WINDOWPOS_CENTERED_DISPLAY(GameWindow::GetDisplay());\n\n        GameWindow::SetDimensions(mode.w, mode.h, centre, centre);\n    };\n\n    Config::Monitor.Callback = [](ConfigDef<int32_t>* def)\n    {\n        GameWindow::SetDisplay(def->Value);\n    };\n\n    Config::Fullscreen.Callback = [](ConfigDef<bool>* def)\n    {\n        GameWindow::SetFullscreen(def->Value);\n        GameWindow::SetDisplay(Config::Monitor);\n    };\n\n    Config::ResolutionScale.Callback = [](ConfigDef<float>* def)\n    {\n        def->Value = std::clamp(def->Value, 0.25f, 2.0f);\n    };\n}\n\nvoid Config::Load()\n{\n    if (!s_isCallbacksCreated)\n    {\n        CreateCallbacks();\n        s_isCallbacksCreated = true;\n    }\n\n    auto configPath = GetConfigPath();\n\n    if (!std::filesystem::exists(configPath))\n    {\n        Config::Save();\n        return;\n    }\n\n    try\n    {\n        toml::parse_result toml;\n        std::ifstream tomlStream(configPath);\n\n        if (tomlStream.is_open())\n            toml = toml::parse(tomlStream);\n\n        for (auto def : g_configDefinitions)\n        {\n            def->ReadValue(toml);\n\n#if _DEBUG\n            LOGFN_UTILITY(\"{} (0x{:X})\", def->GetDefinition().c_str(), (intptr_t)def->GetValue());\n#endif\n        }\n    }\n    catch (toml::parse_error& err)\n    {\n        LOGFN_ERROR(\"Failed to parse configuration: {}\", err.what());\n    }\n}\n\nvoid Config::Save()\n{\n    LOGN(\"Saving configuration...\");\n\n    auto userPath = GetUserPath();\n\n    if (!std::filesystem::exists(userPath))\n        std::filesystem::create_directory(userPath);\n\n    std::string result;\n    std::string section;\n\n    for (auto def : g_configDefinitions)\n    {\n        if (def->IsHidden())\n            continue;\n\n        auto isFirstSection = section.empty();\n        auto isDefWithSection = section != def->GetSection();\n        auto tomlDef = def->GetDefinition(isDefWithSection);\n\n        section = def->GetSection();\n\n        // Don't output prefix space for first section.\n        if (!isFirstSection && isDefWithSection)\n            result += '\\n';\n\n        result += tomlDef + '\\n';\n    }\n\n    std::ofstream out(GetConfigPath());\n\n    if (out.is_open())\n    {\n        out << result;\n        out.close();\n    }\n    else\n    {\n        LOGN_ERROR(\"Failed to write configuration.\");\n    }\n}\n\nbool Config::IsControllerIconsPS3()\n{\n    auto result = Config::ControllerIcons == EControllerIcons::PlayStation;\n\n    if (Config::ControllerIcons == EControllerIcons::Auto)\n        result = hid::g_inputDeviceController == hid::EInputDevice::PlayStation;\n\n    return result;\n}\n\n"
  },
  {
    "path": "MarathonRecomp/user/config.h",
    "content": "#pragma once\n\n#include <locale/locale.h>\n\nclass IConfigDef\n{\npublic:\n    virtual ~IConfigDef() = default;\n    virtual bool IsHidden() = 0;\n    virtual void SetHidden(bool hidden) = 0;\n    virtual void ReadValue(toml::v3::ex::parse_result& toml) = 0;\n    virtual void MakeDefault() = 0;\n    virtual std::string_view GetSection() const = 0;\n    virtual std::string_view GetName() const = 0;\n    virtual std::string GetNameLocalised(ELanguage language) const = 0;\n    virtual std::string GetDescription(ELanguage language) const = 0;\n    virtual bool IsDefaultValue() const = 0;\n    virtual const void* GetValue() const = 0;\n    virtual std::string GetValueLocalised(ELanguage language) const = 0;\n    virtual std::string GetValueDescription(ELanguage language) const = 0;\n    virtual std::string GetDefinition(bool withSection = false) const = 0;\n    virtual std::string ToString(bool strWithQuotes = true) const = 0;\n    virtual void GetLocaleStrings(std::vector<std::string_view>& localeStrings) const = 0;\n    virtual void SnapToNearestAccessibleValue(bool searchUp) = 0;\n    virtual bool RequiresRestart() = 0;\n    virtual void UpdateStore() = 0;\n    virtual bool IsValueChanged() = 0;\n};\n\n#define CONFIG_LOCALE            std::unordered_map<ELanguage, std::tuple<std::string, std::string>>\n#define CONFIG_ENUM_LOCALE(type) std::unordered_map<ELanguage, std::unordered_map<type, std::tuple<std::string, std::string>>>\n\n#define CONFIG_CALLBACK(name)       if (name.Callback) name.Callback(&name)\n#define CONFIG_LOCK_CALLBACK(name)  if (name.LockCallback) name.LockCallback(&name)\n#define CONFIG_APPLY_CALLBACK(name) if (name.ApplyCallback) name.ApplyCallback(&name)\n\n#define WINDOWPOS_CENTRED        0x2FFF0000\n\nextern std::vector<IConfigDef*> g_configDefinitions;\n\nenum class EVoiceLanguage : uint32_t\n{\n    English,\n    Japanese\n};\n\nenum class ECameraRotationMode : uint32_t\n{\n    Normal,\n    Reverse\n};\n\nenum class ELightDash : uint32_t\n{\n    X,\n    Y\n};\n\nenum class ESlidingAttack : uint32_t\n{\n    B,\n    X\n};\n\nenum class EControllerIcons : uint32_t\n{\n    Auto,\n    Xbox,\n    PlayStation\n};\n\nenum class EChannelConfiguration : uint32_t\n{\n    Stereo,\n    Surround\n};\n\nenum class EGraphicsAPI : uint32_t\n{\n    Auto,\n#ifdef MARATHON_RECOMP_D3D12\n    D3D12,\n#endif\n#ifdef MARATHON_RECOMP_METAL\n    Metal,\n#endif\n    Vulkan\n};\n\nenum class EWindowState : uint32_t\n{\n    Normal,\n    Maximised\n};\n\nenum class EAspectRatio : uint32_t\n{\n    Auto,\n    Original\n};\n\nenum class ETripleBuffering : uint32_t\n{\n    Auto,\n    On,\n    Off\n};\n\nstatic constexpr int32_t FPS_MIN = 15;\nstatic constexpr int32_t FPS_MAX = 241;\n\nenum class EAntiAliasing : uint32_t\n{\n    Off = 0,\n    MSAA2x = 2,\n    MSAA4x = 4,\n    MSAA8x = 8\n};\n\nenum class EShadowResolution : int32_t\n{\n    x512 = 512,\n    x1024 = 1024,\n    x2048 = 2048,\n    x4096 = 4096,\n    x8192 = 8192\n};\n\nenum class EReflectionResolution : int32_t\n{\n    Eighth,\n    Quarter,\n    Half,\n    Full\n};\n\nenum class ERadialBlur : uint32_t\n{\n    Off,\n    Original,\n    Enhanced\n};\n\nenum class ECutsceneAspectRatio : uint32_t\n{\n    Original,\n    Unlocked\n};\n\nenum class EUIAlignmentMode : uint32_t\n{\n    Edge,\n    Centre\n};\n\nenum class EPlayerCharacter : uint32_t\n{\n    Sonic,\n    Shadow,\n    Silver,\n    Blaze,\n    Amy,\n    Tails,\n    Rouge,\n    Knuckles\n};\n\ntemplate<typename T, bool isHidden = false>\nclass ConfigDef final : public IConfigDef\n{\n    T m_storedValue{};\n\npublic:\n    std::string Section{};\n    std::string Name{};\n    CONFIG_LOCALE* Locale{};\n    T DefaultValue{};\n    T Value{ DefaultValue };\n    std::set<T> InaccessibleValues{};\n    std::unordered_map<std::string, T>* EnumTemplate{};\n    std::map<T, std::string> EnumTemplateReverse{};\n    CONFIG_ENUM_LOCALE(T)* EnumLocale{};\n    std::function<void(ConfigDef<T, isHidden>*)> Callback;\n    std::function<void(ConfigDef<T, isHidden>*)> LockCallback;\n    std::function<void(ConfigDef<T, isHidden>*)> ApplyCallback;\n    bool IsLoadedFromConfig{};\n    bool IsRestartRequired{};\n\n    // CONFIG_DEFINE\n    ConfigDef(std::string section, std::string name, T defaultValue, bool requiresRestart);\n\n    // CONFIG_DEFINE_LOCALISED\n    ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, bool requiresRestart);\n\n    // CONFIG_DEFINE_ENUM\n    ConfigDef(std::string section, std::string name, T defaultValue, bool requiresRestart, std::unordered_map<std::string, T>* enumTemplate);\n\n    // CONFIG_DEFINE_ENUM_LOCALISED\n    ConfigDef(std::string section, std::string name, CONFIG_LOCALE* nameLocale, T defaultValue, bool requiresRestart, std::unordered_map<std::string, T>* enumTemplate, CONFIG_ENUM_LOCALE(T)* enumLocale);\n\n    ConfigDef(const ConfigDef&) = delete;\n    ConfigDef(ConfigDef&&) = delete;\n    ~ConfigDef();\n\n    bool IsHidden() override;\n    void SetHidden(bool hidden) override;\n    void ReadValue(toml::v3::ex::parse_result& toml) override;\n    void MakeDefault() override;\n    std::string_view GetSection() const override;\n    std::string_view GetName() const override;\n    std::string GetNameLocalised(ELanguage language) const override;\n    std::string GetDescription(ELanguage language) const override;\n    bool IsDefaultValue() const override;\n    const void* GetValue() const override;\n    std::string GetValueLocalised(ELanguage language) const override;\n    std::string GetValueDescription(ELanguage language) const override;\n    std::string GetDefinition(bool withSection = false) const override;\n    std::string ToString(bool strWithQuotes = true) const override;\n    void GetLocaleStrings(std::vector<std::string_view>& localeStrings) const override;\n    void SnapToNearestAccessibleValue(bool searchUp) override;\n    bool RequiresRestart() override;\n    void UpdateStore() override;\n    bool IsValueChanged() override;\n\n    operator T() const\n    {\n        return Value;\n    }\n\n    void operator=(const T& other)\n    {\n        Value = other;\n    }\n};\n\n#define CONFIG_DECLARE(type, name)                                                       static ConfigDef<type>       name;\n#define CONFIG_DECLARE_HIDDEN(type, name)                                                static ConfigDef<type, true> name;\n\n#define CONFIG_DEFINE(section, type, name, defaultValue, requiresRestart)                CONFIG_DECLARE(type, name)\n#define CONFIG_DEFINE_HIDDEN(section, type, name, defaultValue, requiresRestart)         CONFIG_DECLARE_HIDDEN(type, name)\n#define CONFIG_DEFINE_LOCALISED(section, type, name, defaultValue, requiresRestart)      CONFIG_DECLARE(type, name)\n#define CONFIG_DEFINE_ENUM(section, type, name, defaultValue, requiresRestart)           CONFIG_DECLARE(type, name)\n#define CONFIG_DEFINE_ENUM_LOCALISED(section, type, name, defaultValue, requiresRestart) CONFIG_DECLARE(type, name)\n\nclass Config\n{\npublic:\n    #include \"config_def.h\"\n\n    static inline bool s_isCallbacksCreated;\n\n    static std::filesystem::path GetConfigPath();\n\n    static void CreateCallbacks();\n    static void Load();\n    static void Save();\n\n    static bool IsControllerIconsPS3();\n};\n"
  },
  {
    "path": "MarathonRecomp/user/config_def.h",
    "content": "// This file gets included in both config.h and config.cpp, with their own macros changing\n// the preprocessed output. The header is only going to have the declarations this way.\n\nCONFIG_DEFINE_ENUM_LOCALISED(\"System\", ELanguage, Language, ELanguage::English, true);\nCONFIG_DEFINE_ENUM_LOCALISED(\"System\", EVoiceLanguage, VoiceLanguage, EVoiceLanguage::English, false);\nCONFIG_DEFINE_LOCALISED(\"System\", bool, Subtitles, true, false);\nCONFIG_DEFINE_LOCALISED(\"System\", bool, Hints, true, false);\nCONFIG_DEFINE_LOCALISED(\"System\", bool, ControlTutorial, true, false);\nCONFIG_DEFINE_LOCALISED(\"System\", bool, Autosave, true, false);\nCONFIG_DEFINE_LOCALISED(\"System\", bool, AchievementNotifications, true, false);\nCONFIG_DEFINE(\"System\", bool, ShowConsole, false, false);\n\nCONFIG_DEFINE_ENUM_LOCALISED(\"Input\", ECameraRotationMode, HorizontalCamera, ECameraRotationMode::Reverse, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Input\", ECameraRotationMode, VerticalCamera, ECameraRotationMode::Normal, false);\nCONFIG_DEFINE_LOCALISED(\"Input\", bool, AllowBackgroundInput, false, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Input\", EControllerIcons, ControllerIcons, EControllerIcons::Auto, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Input\", ELightDash, LightDash, ELightDash::X, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Input\", ESlidingAttack, SlidingAttack, ESlidingAttack::X, false);\n\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_A, SDL_SCANCODE_S, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_B, SDL_SCANCODE_D, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_X, SDL_SCANCODE_A, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_Y, SDL_SCANCODE_W, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_DPadUp, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_DPadDown, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_DPadLeft, SDL_SCANCODE_Q, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_DPadRight, SDL_SCANCODE_E, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_Start, SDL_SCANCODE_RETURN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_Back, SDL_SCANCODE_BACKSPACE, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_LeftTrigger, SDL_SCANCODE_1, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_RightTrigger, SDL_SCANCODE_3, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_LeftBumper, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_RightBumper, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_LeftStickUp, SDL_SCANCODE_UP, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_LeftStickDown, SDL_SCANCODE_DOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_LeftStickLeft, SDL_SCANCODE_LEFT, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_LeftStickRight, SDL_SCANCODE_RIGHT, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_RightStickUp, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_RightStickDown, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_RightStickLeft, SDL_SCANCODE_UNKNOWN, false);\nCONFIG_DEFINE_ENUM(\"Bindings\", SDL_Scancode, Key_RightStickRight, SDL_SCANCODE_UNKNOWN, false);\n\nCONFIG_DEFINE_LOCALISED(\"Audio\", float, MasterVolume, 1.0f, false);\nCONFIG_DEFINE_LOCALISED(\"Audio\", float, MusicVolume, 0.6f, false);\nCONFIG_DEFINE_LOCALISED(\"Audio\", float, EffectsVolume, 0.6f, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Audio\", EChannelConfiguration, ChannelConfiguration, EChannelConfiguration::Stereo, true);\nCONFIG_DEFINE_LOCALISED(\"Audio\", bool, MuteOnFocusLost, true, false);\nCONFIG_DEFINE_LOCALISED(\"Audio\", bool, MusicAttenuation, false, false);\n\nCONFIG_DEFINE(\"Video\", std::string, GraphicsDevice, \"\", true);\nCONFIG_DEFINE_ENUM(\"Video\", EGraphicsAPI, GraphicsAPI, EGraphicsAPI::Auto, true);\nCONFIG_DEFINE(\"Video\", int32_t, WindowX, WINDOWPOS_CENTRED, false);\nCONFIG_DEFINE(\"Video\", int32_t, WindowY, WINDOWPOS_CENTRED, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", int32_t, WindowSize, -1, false);\nCONFIG_DEFINE(\"Video\", int32_t, WindowWidth, 1280, false);\nCONFIG_DEFINE(\"Video\", int32_t, WindowHeight, 720, false);\nCONFIG_DEFINE_ENUM(\"Video\", EWindowState, WindowState, EWindowState::Normal, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", int32_t, Monitor, 0, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", EAspectRatio, AspectRatio, EAspectRatio::Auto, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", float, ResolutionScale, 1.0f, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", bool, Fullscreen, true, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", bool, VSync, true, false);\nCONFIG_DEFINE_ENUM(\"Video\", ETripleBuffering, TripleBuffering, ETripleBuffering::Auto, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", int32_t, FPS, 60, false);\nCONFIG_DEFINE(\"Video\", bool, ShowFPS, false, false);\nCONFIG_DEFINE(\"Video\", uint32_t, MaxFrameLatency, 2, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", float, Brightness, 0.5f, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", EAntiAliasing, AntiAliasing, EAntiAliasing::MSAA4x, false);\nCONFIG_DEFINE_LOCALISED(\"Video\", bool, TransparencyAntiAliasing, true, false);\nCONFIG_DEFINE(\"Video\", uint32_t, AnisotropicFiltering, 16, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", EShadowResolution, ShadowResolution, EShadowResolution::x4096, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", EReflectionResolution, ReflectionResolution, EReflectionResolution::Half, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", ERadialBlur, RadialBlur, ERadialBlur::Original, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", ECutsceneAspectRatio, CutsceneAspectRatio, ECutsceneAspectRatio::Original, false);\nCONFIG_DEFINE_ENUM_LOCALISED(\"Video\", EUIAlignmentMode, UIAlignmentMode, EUIAlignmentMode::Edge, false);\n\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, AntigravityRetainsMomentum, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, ControllableBoundAttack, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, ControllableSpinkick, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, ControllableTeleportDash, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, DisableDWMRoundedCorners, false, true);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, DisableEdgeGrabLeftover, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, DisableKingdomValleyMist, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, DisableLowResolutionFontOnCustomUI, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, DisablePushState, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, DisableTitleInputDelay, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, EnableDebugMode, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, FixPowerUpJingleDuration, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, HUDToggleKey, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, InfiniteLives, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, MidairControlForMachSpeed, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, MidairControlForSnowboards, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, RestoreChainJumpFlips, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, RestoreChaosBoostJump, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, RestoreChaosSpearFlips, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, RestoreContextualHUDColours, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, RestoreDemoCameraMode, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, RestoreSonicActionGauge, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, SkipIntroLogos, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, TailsGauge, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, UnlimitedAntigravity, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, UseOfficialAchievementText, false, false);\nCONFIG_DEFINE_HIDDEN(\"Codes\", bool, UseOfficialTitleOnTitleBar, false, true);\n\nCONFIG_DEFINE(\"Update\", time_t, LastChecked, 0, false);\n"
  },
  {
    "path": "MarathonRecomp/user/paths.cpp",
    "content": "#include \"paths.h\"\n#include <os/process.h>\n\nstd::filesystem::path g_executableRoot = os::process::GetExecutableRoot();\nstd::filesystem::path g_userPath = BuildUserPath();\n\nbool CheckPortable()\n{\n    return std::filesystem::exists(g_executableRoot / \"portable.txt\");\n}\n\nstd::filesystem::path BuildUserPath()\n{\n    if (CheckPortable())\n        return g_executableRoot;\n\n    std::filesystem::path userPath;\n\n#if defined(_WIN32)\n    PWSTR knownPath = NULL;\n    if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &knownPath) == S_OK)\n        userPath = std::filesystem::path{ knownPath } / USER_DIRECTORY;\n\n    CoTaskMemFree(knownPath);\n#elif defined(__linux__) || defined(__APPLE__)\n    const char* homeDir = getenv(\"HOME\");\n#if defined(__linux__)\n    if (homeDir == nullptr)\n    {\n        homeDir = getpwuid(getuid())->pw_dir;\n    }\n#endif\n\n    if (homeDir != nullptr)\n    {\n        // Prefer to store in the .config directory if it exists. Use the home directory otherwise.\n        std::filesystem::path homePath = homeDir;\n#if defined(__linux__)\n        std::filesystem::path configPath = homePath / \".config\";\n#else\n        std::filesystem::path configPath = homePath / \"Library\" / \"Application Support\";\n#endif\n        if (std::filesystem::exists(configPath))\n            userPath = configPath / USER_DIRECTORY;\n        else\n            userPath = homePath / (\".\" USER_DIRECTORY);\n    }\n#else\n    static_assert(false, \"GetUserPath() not implemented for this platform.\");\n#endif\n\n    return userPath;\n}\n\nconst std::filesystem::path& GetUserPath()\n{\n    return g_userPath;\n}\n"
  },
  {
    "path": "MarathonRecomp/user/paths.h",
    "content": "#pragma once\n\n#include <mod/mod_loader.h>\n\n#define USER_DIRECTORY \"MarathonRecomp\"\n\n#ifndef GAME_INSTALL_DIRECTORY\n#define GAME_INSTALL_DIRECTORY \".\"\n#endif\n\nextern std::filesystem::path g_executableRoot;\ninline std::unordered_map<std::string, std::filesystem::path> g_pathCache;\n\nbool CheckPortable();\nstd::filesystem::path BuildUserPath();\nconst std::filesystem::path& GetUserPath();\n\ninline std::filesystem::path GetGamePath()\n{\n#ifdef __APPLE__\n    // On macOS, there is the expectation that the app may be installed to\n    // /Applications/, and the bundle should not be modified. Thus we need\n    // to install game files to the user directory instead of next to the app.\n    return GetUserPath();\n#else\n    return GAME_INSTALL_DIRECTORY;\n#endif\n}\n\ninline std::filesystem::path GetSavePath(bool checkForMods)\n{\n    if (checkForMods && !ModLoader::s_saveFilePath.empty())\n        return ModLoader::s_saveFilePath.parent_path();\n    else\n        return GetUserPath() / \"save\";\n}\n\n// Returned file name may not necessarily be\n// equal to SYS-DATA as mods can assign anything.\ninline std::filesystem::path GetSaveFilePath(bool checkForMods)\n{\n    if (checkForMods && !ModLoader::s_saveFilePath.empty())\n        return ModLoader::s_saveFilePath;\n    else\n        return GetSavePath(false) / \"SonicNextSaveData.bin\";\n}\n\nstatic std::string toLower(std::string str) {\n    std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); });\n    return str;\n};\n\ninline void BuildPathCache(const std::string& gamePath) {\n    for (const auto& entry : std::filesystem::recursive_directory_iterator(gamePath)) {\n        std::string fullPath = entry.path().string();\n        std::string key = toLower(fullPath);\n        g_pathCache[key] = entry.path();\n    }\n}\n\ninline std::filesystem::path FindInPathCache(const std::string& targetPath) {\n    std::string key = toLower(targetPath);\n    auto it = g_pathCache.find(key);\n    if (it != g_pathCache.end()) {\n        return it->second;\n    }\n    return {};\n}"
  },
  {
    "path": "MarathonRecomp/user/registry.cpp",
    "content": "#include \"registry.h\"\n#include <os/process.h>\n#include <os/registry.h>\n\nvoid Registry::Save()\n{\n    os::registry::WriteValue(STR(ExecutableFilePath), os::process::GetExecutablePath());\n}\n"
  },
  {
    "path": "MarathonRecomp/user/registry.h",
    "content": "#pragma once\n\nclass Registry\n{\npublic:\n    static void Save();\n};\n"
  },
  {
    "path": "MarathonRecomp/utils/bit_stream.cpp",
    "content": "/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2021 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n#include \"bit_stream.h\"\n\nBitStream::BitStream(uint8_t* buffer, size_t size_in_bits)\n    : buffer_(buffer), size_bits_(size_in_bits) {}\n\nBitStream::~BitStream() = default;\n\nvoid BitStream::SetOffset(size_t offset_bits) {\n//   assert_false(offset_bits > size_bits_);\n  offset_bits_ = std::min(offset_bits, size_bits_);\n}\n\nsize_t BitStream::BitsRemaining() { return size_bits_ - offset_bits_; }\n\nbool BitStream::IsOffsetValid(size_t num_bits) {\n  size_t offset_bytes = offset_bits_ >> 3;\n  size_t rel_offset_bits = offset_bits_ - (offset_bytes << 3);\n\n  if (rel_offset_bits && int32_t(num_bits - 8 - rel_offset_bits) < 0) {\n    return false;\n  }\n  return true;\n}\n\nuint64_t BitStream::Peek(size_t num_bits) {\n  // FYI: The reason we can't copy more than 57 bits is:\n  // 57 = 7 * 8 + 1 - that can only span a maximum of 8 bytes.\n  // We can't read in 9 bytes (easily), so we limit it.\n//   assert_false(num_bits > 57);\n//   assert_false(offset_bits_ + num_bits > size_bits_);\n\n  size_t offset_bytes = std::min(offset_bits_ >> 3, (size_bits_ - 64) >> 3);\n  size_t rel_offset_bits = offset_bits_ - (offset_bytes << 3);\n\n  // offset -->\n  // ..[junk]..| target bits |....[junk].............\n  uint64_t bits = *(uint64_t*)(buffer_ + offset_bytes);\n\n  // We need the data in little endian.\n  // TODO: Have a flag specifying endianness of data?\n  bits = ByteSwap(bits);\n\n  // Shift right\n  // .....[junk]........| target bits |\n  bits >>= 64 - (rel_offset_bits + num_bits);\n\n  // AND with mask\n  // ...................| target bits |\n  bits &= (1ULL << num_bits) - 1;\n\n  return bits;\n}\n\nuint64_t BitStream::Read(size_t num_bits) {\n  uint64_t val = Peek(num_bits);\n  Advance(num_bits);\n\n  return val;\n}\n\n// TODO: This is totally not tested!\nbool BitStream::Write(uint64_t val, size_t num_bits) {\n//   assert_false(num_bits > 57);\n//   assert_false(offset_bits_ + num_bits >= size_bits_);\n\n  size_t offset_bytes = offset_bits_ >> 3;\n  size_t rel_offset_bits = offset_bits_ - (offset_bytes << 3);\n\n  // Construct a mask\n  uint64_t mask = (1ULL << num_bits) - 1;\n  mask <<= 64 - (rel_offset_bits + num_bits);\n  mask = ~mask;\n\n  // Shift the value left into position.\n  val <<= 64 - (rel_offset_bits + num_bits);\n\n  // offset ----->\n  // ....[junk]...| target bits w/ junk |....[junk]......\n  uint64_t bits = *(uint64_t*)(buffer_ + offset_bytes);\n\n  // AND with mask\n  // ....[junk]...| target bits (0) |........[junk]......\n  bits &= mask;\n\n  // OR with val\n  // ....[junk]...| target bits (val) |......[junk]......\n  bits |= val;\n\n  // Store into the bitstream.\n  *(uint64_t*)(buffer_ + offset_bytes) = bits;\n\n  // Advance the bitstream forward.\n  Advance(num_bits);\n\n  return true;\n}\n\nsize_t BitStream::Copy(uint8_t* dest_buffer, size_t num_bits) {\n  size_t offset_bytes = offset_bits_ >> 3;\n  size_t rel_offset_bits = offset_bits_ - (offset_bytes << 3);\n  size_t bits_left = num_bits;\n  size_t out_offset_bytes = 0;\n\n  // First: Copy the first few bits up to a byte boundary.\n  if (rel_offset_bits) {\n    uint64_t bits = Peek(8 - rel_offset_bits);\n    uint8_t clear_mask = ~((uint8_t(1) << rel_offset_bits) - 1);\n    dest_buffer[out_offset_bytes] &= clear_mask;\n    dest_buffer[out_offset_bytes] |= (uint8_t)bits;\n\n    bits_left -= 8 - rel_offset_bits;\n    Advance(8 - rel_offset_bits);\n    out_offset_bytes++;\n  }\n\n  // Second: Use memcpy for the bytes left.\n  if (bits_left >= 8) {\n    std::memcpy(dest_buffer + out_offset_bytes,\n                buffer_ + offset_bytes + out_offset_bytes, bits_left / 8);\n    out_offset_bytes += (bits_left / 8);\n    Advance((bits_left / 8) * 8);\n    bits_left -= (bits_left / 8) * 8;\n  }\n\n  // Third: Copy the last few bits.\n  if (bits_left) {\n    uint64_t bits = Peek(bits_left);\n    bits <<= 8 - bits_left;\n\n    uint8_t clear_mask = ((uint8_t(1) << bits_left) - 1);\n    dest_buffer[out_offset_bytes] &= clear_mask;\n    dest_buffer[out_offset_bytes] |= (uint8_t)bits;\n    Advance(bits_left);\n  }\n\n  // Return the bit offset to the copied bits.\n  return rel_offset_bits;\n}\n\nvoid BitStream::Advance(size_t num_bits) { SetOffset(offset_bits_ + num_bits); }"
  },
  {
    "path": "MarathonRecomp/utils/bit_stream.h",
    "content": "/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2015 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n#pragma once\n\nclass BitStream {\npublic:\n  BitStream(uint8_t *buffer, size_t size_in_bits);\n  ~BitStream();\n\n  const uint8_t *buffer() const { return buffer_; }\n  uint8_t *buffer() { return buffer_; }\n  size_t offset_bits() const { return offset_bits_; }\n  size_t size_bits() const { return size_bits_; }\n\n  void Advance(size_t num_bits);\n  void SetOffset(size_t offset_bits);\n  size_t BitsRemaining();\n  bool IsOffsetValid(size_t num_bits);\n\n  // Note: num_bits MUST be in the range 0-57 (inclusive)\n  uint64_t Peek(size_t num_bits);\n  uint64_t Read(size_t num_bits);\n  bool Write(uint64_t val, size_t num_bits); // TODO(DrChat): Not tested!\n\n  size_t Copy(uint8_t *dest_buffer, size_t num_bits);\n\nprivate:\n  uint8_t *buffer_ = nullptr;\n  size_t offset_bits_ = 0;\n  size_t size_bits_ = 0;\n};"
  },
  {
    "path": "MarathonRecomp/utils/ring_buffer.cpp",
    "content": "/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2015 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n\n#include \"ring_buffer.h\"\n\nvoid RingBuffer::AdvanceRead(size_t _count) {\n  ring_size_t count = static_cast<ring_size_t>(_count);\n  if (read_offset_ + count < capacity_) {\n    read_offset_ += count;\n  } else {\n    ring_size_t left_half = capacity_ - read_offset_;\n    ring_size_t right_half = count - left_half;\n    read_offset_ = right_half;\n  }\n}\n\nvoid RingBuffer::AdvanceWrite(size_t _count) {\n  ring_size_t count = static_cast<ring_size_t>(_count);\n\n  if (write_offset_ + count < capacity_) {\n    write_offset_ += count;\n  } else {\n    ring_size_t left_half = capacity_ - write_offset_;\n    ring_size_t right_half = count - left_half;\n    write_offset_ = right_half;\n  }\n}\n\nRingBuffer::ReadRange RingBuffer::BeginRead(size_t _count) {\n  ring_size_t count =\n      std::min<ring_size_t>(static_cast<ring_size_t>(_count), capacity_);\n  if (!!(count)) [[likely]] {\n    if (read_offset_ + count < capacity_) {\n      return {buffer_ + read_offset_, nullptr, count, 0};\n    } else {\n      ring_size_t left_half = capacity_ - read_offset_;\n      ring_size_t right_half = count - left_half;\n      return {buffer_ + read_offset_, buffer_, left_half, right_half};\n    }\n  } else {\n    return {0};\n  }\n}\n\nvoid RingBuffer::EndRead(ReadRange read_range) {\n  if (read_range.second) {\n    read_offset_ = read_range.second_length;\n  } else {\n    read_offset_ += read_range.first_length;\n  }\n}\n\nsize_t RingBuffer::Read(uint8_t *buffer, size_t _count) {\n  ring_size_t count = static_cast<ring_size_t>(_count);\n  count = std::min(count, capacity_);\n  if (!count) {\n    return 0;\n  }\n\n  // Sanity check: Make sure we don't read over the write offset.\n  if (read_offset_ < write_offset_) {\n    assert(read_offset_ + count <= write_offset_);\n  } else if (read_offset_ + count >= capacity_) {\n    __attribute__((unused)) ring_size_t left_half = capacity_ - read_offset_;\n\n    assert(count - left_half <= write_offset_);\n  }\n\n  if (read_offset_ + count < capacity_) {\n    std::memcpy(buffer, buffer_ + read_offset_, count);\n    read_offset_ += count;\n  } else {\n    ring_size_t left_half = capacity_ - read_offset_;\n    ring_size_t right_half = count - left_half;\n    std::memcpy(buffer, buffer_ + read_offset_, left_half);\n    std::memcpy(buffer + left_half, buffer_, right_half);\n    read_offset_ = right_half;\n  }\n\n  return count;\n}\n\nsize_t RingBuffer::Write(const uint8_t *buffer, size_t _count) {\n  ring_size_t count = static_cast<ring_size_t>(_count);\n  count = std::min(count, capacity_);\n  if (!count) {\n    return 0;\n  }\n\n  // Sanity check: Make sure we don't write over the read offset.\n  if (write_offset_ < read_offset_) {\n    assert(write_offset_ + count <= read_offset_);\n  } else if (write_offset_ + count >= capacity_) {\n    __attribute__((unused)) size_t left_half = capacity_ - write_offset_;\n    assert(count - left_half <= read_offset_);\n  }\n\n  if (write_offset_ + count < capacity_) {\n    std::memcpy(buffer_ + write_offset_, buffer, count);\n    write_offset_ += count;\n  } else {\n    ring_size_t left_half = capacity_ - write_offset_;\n    ring_size_t right_half = count - left_half;\n    std::memcpy(buffer_ + write_offset_, buffer, left_half);\n    std::memcpy(buffer_, buffer + left_half, right_half);\n    write_offset_ = right_half;\n  }\n\n  return count;\n}"
  },
  {
    "path": "MarathonRecomp/utils/ring_buffer.h",
    "content": "/**\n ******************************************************************************\n * Xenia : Xbox 360 Emulator Research Project                                 *\n ******************************************************************************\n * Copyright 2015 Ben Vanik. All rights reserved.                             *\n * Released under the BSD license - see LICENSE in the root for more details. *\n ******************************************************************************\n */\n#pragma once\n\nusing ring_size_t = uint32_t;\nclass RingBuffer {\n public:\n  RingBuffer(uint8_t* buffer, size_t capacity)\n      : buffer_(buffer), capacity_(static_cast<ring_size_t>(capacity)), read_offset_(0), write_offset_(0) {}\n\n  uint8_t* buffer() const { return buffer_; }\n  ring_size_t capacity() const { return capacity_; }\n  bool empty() const { return read_offset_ == write_offset_; }\n\n  ring_size_t read_offset() const { return read_offset_; }\n  uintptr_t read_ptr() const {\n    return uintptr_t(buffer_) + static_cast<uintptr_t>(read_offset_);\n  }\n\n  // todo: offset/ capacity_ is probably always 1 when its over, just check and\n  // subtract instead\n  void set_read_offset(size_t offset) { read_offset_ = offset % capacity_; }\n\n  ring_size_t write_offset() const { return write_offset_; }\n  uintptr_t write_ptr() const { return uintptr_t(buffer_) + write_offset_; }\n  void set_write_offset(size_t offset) {\n    write_offset_ = static_cast<ring_size_t>(offset) % capacity_;\n  }\n  ring_size_t write_count() const {\n    if (read_offset_ == write_offset_) {\n      return capacity_;\n    } else if (write_offset_ < read_offset_) {\n      return read_offset_ - write_offset_;\n    } else {\n      return (capacity_ - write_offset_) + read_offset_;\n    }\n  }\n\n  void AdvanceRead(size_t count);\n  void AdvanceWrite(size_t count);\n\n  struct ReadRange {\n    const uint8_t* first;\n\n    const uint8_t* second;\n    ring_size_t first_length;\n    ring_size_t second_length;\n  };\n  ReadRange BeginRead(size_t count);\n  void EndRead(ReadRange read_range);\n\n  size_t Read(uint8_t* buffer, size_t count);\n  template <typename T>\n  size_t Read(T* buffer, size_t count) {\n    return Read(reinterpret_cast<uint8_t*>(buffer), count);\n  }\n\n  template <typename T>\n  T Read() {\n    static_assert(std::is_fundamental<T>::value,\n                  \"Immediate read only supports basic types!\");\n\n    T imm;\n    size_t read = Read(reinterpret_cast<uint8_t*>(&imm), sizeof(T));\n    assert(read == sizeof(T));\n    return imm;\n  }\n\n  size_t Write(const uint8_t* buffer, size_t count);\n  template <typename T>\n  size_t Write(const T* buffer, size_t count) {\n    return Write(reinterpret_cast<const uint8_t*>(buffer), count);\n  }\n\n  template <typename T>\n  size_t Write(T& data) {\n    return Write(reinterpret_cast<const uint8_t*>(&data), sizeof(T));\n  }\n\n private:\n  uint8_t* buffer_ = nullptr;\n  ring_size_t capacity_ = 0;\n  ring_size_t read_offset_ = 0;\n  ring_size_t write_offset_ = 0;\n};"
  },
  {
    "path": "MarathonRecomp/version.cmake",
    "content": "# version.cmake - written by hyperbx\n# Generates a compilation unit from template files for version information.\n\n# OUTPUT_FILE   : the original output file from a previous generation.\n# TEMPLATE_FILE : the corresponding template file that was used to generate the output file.\nfunction(CheckOutputFile OUTPUT_FILE TEMPLATE_FILE)\n    if (NOT OUTPUT_FILE)\n        message(FATAL_ERROR \"OUTPUT_FILE not specified.\")\n    endif()\n\n    if (NOT TEMPLATE_FILE)\n        message(FATAL_ERROR \"TEMPLATE_FILE not specified.\")\n    endif()\n\n    if (EXISTS \"${OUTPUT_FILE}\")\n        # Read original output file.\n        file(READ \"${OUTPUT_FILE}\" ORIGINAL_CONTENT)\n\n        # Read template file and configure.\n        file(READ \"${TEMPLATE_FILE}\" TEMPLATE_CONTENT)\n        string(CONFIGURE \"${TEMPLATE_CONTENT}\" TEMPLATE_FILE_FINAL_CONTENT @ONLY)\n\n        # Check if configured template matches the original output file and replace it if not.\n        if (NOT ORIGINAL_CONTENT STREQUAL TEMPLATE_FILE_FINAL_CONTENT)\n            message(\"-- Creating ${OUTPUT_FILE}\")\n            file(WRITE \"${OUTPUT_FILE}\" \"${TEMPLATE_FILE_FINAL_CONTENT}\")\n        endif()\n    else()\n        message(\"-- Creating ${OUTPUT_FILE}\")\n        configure_file(\"${TEMPLATE_FILE}\" \"${OUTPUT_FILE}\" @ONLY)\n    endif()\nendfunction()\n\n# VERSION_TXT : the input text file containing the milestone, major, minor and revision variables.\nfunction(ParseVersionInfo VERSION_TXT)\n    if (NOT VERSION_TXT)\n        message(FATAL_ERROR \"VERSION_TXT not specified.\")\n    endif()\n    \n    if (NOT EXISTS \"${VERSION_TXT}\")\n        message(FATAL_ERROR \"No such file: ${VERSION_TXT}\")\n    endif()\n\n    file(READ \"${VERSION_TXT}\" FILE_CONTENT)\n    string(REGEX REPLACE \"\\r?\\n\" \";\" FILE_LINES \"${FILE_CONTENT}\")\n\n    foreach(LINE ${FILE_LINES})\n        if (LINE STREQUAL \"\")\n            continue()\n        endif()\n\n        # Find key/value pair match.\n        string(REGEX MATCH \"([A-Za-z_]+)=\\\\\\\"?([^\\\"]+)\\\\\\\"?\" MATCH \"${LINE}\")\n\n        if (MATCH)\n            # Extract key/value pairs.\n            string(REGEX REPLACE \"([A-Za-z_]+)=.*\" \"\\\\1\" KEY \"${MATCH}\")\n            string(REGEX REPLACE \"[A-Za-z_]+=\\\\\\\"?([^\\\"]+)\\\\\\\"?\" \"\\\\1\" VALUE \"${MATCH}\")\n\n            # Set environment variable.\n            set(\"${KEY}\" \"${VALUE}\" CACHE INTERNAL \"${KEY}\")\n        endif()\n    endforeach()\nendfunction()\n\n# VERSION_TXT     : the input text file containing the milestone, major, minor and revision variables.\n# OUTPUT_CSV      : the output string will be formatted as #,#,# without any alphabetic characters (optional).\n# BUILD_TYPE      : the current build configuration (e.g. \"Release\", \"RelWithDebInfo\", \"Debug\") (optional).\n# SHOW_GIT_INFO   : the Git commit hash and branch name should be appended to the version string (optional).\n# SHOW_BUILD_TYPE : the build type should be appended to the version string (optional).\n# OUTPUT_VAR      : the output variable to store the version string in.\nfunction(CreateVersionString)\n    cmake_parse_arguments(ARGS \"\" \"VERSION_TXT;OUTPUT_CSV;BUILD_TYPE;SHOW_GIT_INFO;SHOW_BUILD_TYPE;OUTPUT_VAR\" \"\" ${ARGN})\n    \n    if (NOT ARGS_VERSION_TXT)\n        message(FATAL_ERROR \"VERSION_TXT not specified.\")\n    endif()\n\n    if (NOT ARGS_OUTPUT_VAR)\n        message(FATAL_ERROR \"OUTPUT_VAR not specified.\")\n    endif()\n\n    ParseVersionInfo(\"${ARGS_VERSION_TXT}\")\n\n    if (ARGS_OUTPUT_CSV)\n        set(VERSION_STRING \"${VERSION_MAJOR},${VERSION_MINOR},${VERSION_REVISION}\")\n    else()\n        set(VERSION_STRING \"v${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}\")\n\n        if (VERSION_MILESTONE)\n            string(PREPEND VERSION_STRING \"${VERSION_MILESTONE} \")\n        endif()\n\n        if (ARGS_SHOW_GIT_INFO)\n            find_package(Git REQUIRED)\n\n            # Get Git branch name.\n            execute_process(\n                COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD\n                OUTPUT_VARIABLE BRANCH_NAME\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n            )\n\n            set(BRANCH_NAME ${BRANCH_NAME} CACHE INTERNAL \"BRANCH_NAME\")\n\n            # Get Git commit hash.\n            execute_process(\n                COMMAND ${GIT_EXECUTABLE} rev-parse HEAD\n                OUTPUT_VARIABLE COMMIT_HASH\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n            )\n\n            set(COMMIT_HASH ${COMMIT_HASH} CACHE INTERNAL \"COMMIT_HASH\")\n\n            # Get short Git commit hash.\n            execute_process(\n                COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD\n                OUTPUT_VARIABLE COMMIT_HASH_SHORT\n                OUTPUT_STRIP_TRAILING_WHITESPACE\n            )\n\n            set(COMMIT_HASH_SHORT ${COMMIT_HASH_SHORT} CACHE INTERNAL \"COMMIT_HASH_SHORT\")\n    \n            # Append commit hash and branch.\n            if (COMMIT_HASH_SHORT AND BRANCH_NAME)\n                string(APPEND VERSION_STRING \".${COMMIT_HASH_SHORT}-${BRANCH_NAME}\")\n            endif()\n        endif()\n\n        # Append build configuration.\n        if (ARGS_BUILD_TYPE AND ARGS_SHOW_BUILD_TYPE)\n            string(APPEND VERSION_STRING \" (${ARGS_BUILD_TYPE})\")\n        endif()\n    endif()\n\n    if (ARGS_OUTPUT_VAR)\n        set(${ARGS_OUTPUT_VAR} ${VERSION_STRING} PARENT_SCOPE)\n    endif()\nendfunction()\n\n# OUTPUT_DIR      : the output directory of the resulting *.h/*.cpp files.\n# VERSION_TXT     : the input text file containing the milestone, major, minor and revision variables.\n# H_TEMPLATE      : the input template for the header.\n# CXX_TEMPLATE    : the input template for the source file.\n# BUILD_TYPE      : the current build configuration (e.g. \"Release\", \"RelWithDebInfo\", \"Debug\") (optional).\n# SHOW_GIT_INFO   : the Git commit hash and branch name should be appended to the version string (optional).\n# SHOW_BUILD_TYPE : the build type should be appended to the version string (optional).\nfunction(GenerateVersionSources)\n    cmake_parse_arguments(ARGS \"\" \"OUTPUT_DIR;VERSION_TXT;H_TEMPLATE;CXX_TEMPLATE;BUILD_TYPE;SHOW_GIT_INFO;SHOW_BUILD_TYPE\" \"\" ${ARGN})\n\n    message(\"-- Generating version information...\")\n\n    if (NOT ARGS_OUTPUT_DIR)\n        message(FATAL_ERROR \"OUTPUT_DIR not specified.\")\n    endif()\n\n    if (NOT ARGS_VERSION_TXT)\n        message(FATAL_ERROR \"VERSION_TXT not specified.\")\n    endif()\n\n    if (NOT ARGS_H_TEMPLATE)\n        message(FATAL_ERROR \"H_TEMPLATE not specified.\")\n    endif()\n\n    if (NOT ARGS_CXX_TEMPLATE)\n        message(FATAL_ERROR \"CXX_TEMPLATE not specified.\")\n    endif()\n\n    # Required for *.cpp template.\n    set(BUILD_TYPE ${ARGS_BUILD_TYPE})\n\n    CreateVersionString(\n        VERSION_TXT ${ARGS_VERSION_TXT}\n        BUILD_TYPE ${ARGS_BUILD_TYPE}\n        SHOW_GIT_INFO ${ARGS_SHOW_GIT_INFO}\n        SHOW_BUILD_TYPE ${ARGS_SHOW_BUILD_TYPE}\n        OUTPUT_VAR VERSION_STRING\n    )\n\n    message(\"-- Build: ${VERSION_STRING}\")\n\n    get_filename_component(H_TEMPLATE_NAME_WE \"${ARGS_H_TEMPLATE}\" NAME_WE)\n    get_filename_component(CXX_TEMPLATE_NAME_WE \"${ARGS_CXX_TEMPLATE}\" NAME_WE)\n    set(H_OUTPUT_FILE \"${ARGS_OUTPUT_DIR}/${H_TEMPLATE_NAME_WE}.h\")\n    set(CXX_OUTPUT_FILE \"${ARGS_OUTPUT_DIR}/${CXX_TEMPLATE_NAME_WE}.cpp\")\n    \n    CheckOutputFile(\"${H_OUTPUT_FILE}\" \"${ARGS_H_TEMPLATE}\")\n    CheckOutputFile(\"${CXX_OUTPUT_FILE}\" \"${ARGS_CXX_TEMPLATE}\")\nendfunction()\n"
  },
  {
    "path": "MarathonRecomp/xxHashMap.h",
    "content": "#pragma once\n\nstruct xxHash\n{\n    using is_avalanching = void;\n\n    uint64_t operator()(XXH64_hash_t const& x) const noexcept\n    {\n        return x;\n    }\n};\n\ntemplate<typename T>\nusing xxHashMap = ankerl::unordered_dense::map<XXH64_hash_t, T, xxHash>;\n\ninline XXH64_hash_t HashStr(const std::string_view& value)\n{\n    return XXH3_64bits(value.data(), value.size());\n}\n\ntemplate <typename T>\ninline T FindHash(const xxHashMap<T>& map, const XXH64_hash_t hash)\n{\n    auto findResult = map.find(hash);\n\n    if (findResult != map.end())\n        return findResult->second;\n\n    return {};\n}\n"
  },
  {
    "path": "MarathonRecompLib/CMakeLists.txt",
    "content": "project(\"MarathonRecompLib\")\n\nadd_compile_options(\n    -fno-strict-aliasing\n)\n\nif (WIN32)\n    add_compile_options(/fp:strict)\nelse()\n    add_compile_options(-ffp-model=strict)\nendif()\n\ntarget_compile_definitions(XenonRecomp PRIVATE \n    XENON_RECOMP_CONFIG_FILE_PATH=\\\"${CMAKE_CURRENT_SOURCE_DIR}/config/Marathon.toml\\\"\n    XENON_RECOMP_HEADER_FILE_PATH=\\\"${MARATHON_RECOMP_TOOLS_ROOT}/XenonRecomp/XenonUtils/ppc_context.h\\\")\n\nset(MARATHON_RECOMP_PPC_RECOMPILED_SOURCES\n    \"${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_config.h\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_context.h\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_func_mapping.cpp\"\n    \"${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_recomp_shared.h\"\n)\n\nforeach(i RANGE 0 145)\n    list(APPEND MARATHON_RECOMP_PPC_RECOMPILED_SOURCES \"${CMAKE_CURRENT_SOURCE_DIR}/ppc/ppc_recomp.${i}.cpp\")\nendforeach()\n\nadd_custom_command(\n    OUTPUT \n        ${MARATHON_RECOMP_PPC_RECOMPILED_SOURCES}\n    COMMAND \n        $<TARGET_FILE:XenonRecomp>\n    DEPENDS \n        \"${CMAKE_CURRENT_SOURCE_DIR}/private/default.xex\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/config/Marathon.toml\"\n    USES_TERMINAL\n)\n\nadd_custom_command(\n    OUTPUT\n        \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader\"\n    COMMAND\n        $<TARGET_FILE:u8extract> \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader.arc\" \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader\"\n    COMMAND\n        $<TARGET_FILE:u8extract> \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader_lt.arc\" \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader\"\n    DEPENDS\n        \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader.arc\"\n        \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader_lt.arc\"\n)\n\nset(XENOS_RECOMP_ROOT \"${MARATHON_RECOMP_TOOLS_ROOT}/XenosRecomp/XenosRecomp\")\nset(XENOS_RECOMP_INCLUDE \"${XENOS_RECOMP_ROOT}/shader_common.h\")\n\ntarget_compile_definitions(XenosRecomp PRIVATE \n    XENOS_RECOMP_INPUT=\\\"${CMAKE_CURRENT_SOURCE_DIR}/private/shader\\\" \n    XENOS_RECOMP_OUTPUT=\\\"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp\\\"\n    XENOS_RECOMP_INCLUDE_INPUT=\\\"${XENOS_RECOMP_INCLUDE}\\\"\n    MARATHON_RECOMP\n)\n\nfile(GLOB XENOS_RECOMP_SOURCES \n    \"${XENOS_RECOMP_ROOT}/*.h\"\n    \"${XENOS_RECOMP_ROOT}/*.cpp\"\n)\n\nadd_custom_command(\n    OUTPUT \n        \"${CMAKE_CURRENT_SOURCE_DIR}/shader/shader_cache.cpp\"\n    COMMAND\n        $<TARGET_FILE:XenosRecomp>\n    DEPENDS \n        \"${CMAKE_CURRENT_SOURCE_DIR}/private/shader\"\n        ${XENOS_RECOMP_SOURCES}\n        ${XENOS_RECOMP_INCLUDE}\n    USES_TERMINAL\n)\n\nadd_library(MarathonRecompLib\n    ${MARATHON_RECOMP_PPC_RECOMPILED_SOURCES}\n    \"shader/shader_cache.h\"\n    \"shader/shader_cache.cpp\"\n)\n\ntarget_include_directories(MarathonRecompLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})\ntarget_include_directories(MarathonRecompLib PRIVATE \"${MARATHON_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty/simde\")\ntarget_precompile_headers(MarathonRecompLib PUBLIC \"ppc/ppc_recomp_shared.h\")\n"
  },
  {
    "path": "MarathonRecompLib/config/Marathon.toml",
    "content": "[main]\nfile_path = \"../private/default.xex\"\nout_directory_path = \"../ppc\"\nswitch_table_file_path = \"./switch_table.toml\"\n\nskip_lr = false\nskip_msr = false\nctr_as_local = false\nxer_as_local = false\nreserved_as_local = false\ncr_as_local = false\nnon_argument_as_local = false\nnon_volatile_as_local = false\n\nrestgprlr_14_address = 0x826de8e0\nsavegprlr_14_address = 0x826de890\nrestfpr_14_address = 0x826dfb9c\nsavefpr_14_address = 0x826dfb50\nrestvmx_14_address = 0x826e1038\nsavevmx_14_address = 0x826e0da0\nrestvmx_64_address = 0x826e10cc\nsavevmx_64_address = 0x826e0e34\n\nlongjmp_address = 0x826e4f10\nsetjmp_address = 0x826e5030\n\nfunctions = [\n    { address = 0x8264C7C8, size = 0x68 },\n    { address = 0x82636918, size = 0xD0 },\n    { address = 0x8273C438, size = 0x4D8 },\n    { address = 0x824A6180, size = 0x90 },\n    { address = 0x82A97608, size = 0x64 },\n    { address = 0x82775010, size = 0xD4 },\n    { address = 0x8259A648, size = 0x84 },\n    { address = 0x8273C110, size = 0x11C },\n    { address = 0x821677D0, size = 0x7C },\n    { address = 0x825B3A28, size = 0x84 },\n    { address = 0x8273C368, size = 0xD0 },\n    { address = 0x82912600, size = 0x11C },\n    { address = 0x8244B920, size = 0x54 },\n    { address = 0x82912A18, size = 0x254 },\n    { address = 0x824C45B8, size = 0x50 },\n    { address = 0x823D49A8, size = 0x1BC },\n    { address = 0x8250F448, size = 0x98 },\n    { address = 0x82763E28, size = 0x228 },\n    { address = 0x828BFB18, size = 0x48 },\n    { address = 0x82A97670, size = 0x5C },\n    { address = 0x8223DF00, size = 0x144 },\n    { address = 0x828B9CC8, size = 0x80 },\n    { address = 0x8298D248, size = 0x100 },\n    { address = 0x82218068, size = 0xA4 },\n    { address = 0x8293F238, size = 0x114 },\n    { address = 0x824C4550, size = 0x68 },\n    { address = 0x82610860, size = 0x7C },\n    { address = 0x821FDC28, size = 0x90 },\n    { address = 0x82914568, size = 0x78 },\n    { address = 0x8223EFE8, size = 0x14C },\n    { address = 0x826103F0, size = 0x7C },\n    { address = 0x82A975A0, size = 0x68 },\n    { address = 0x8253B760, size = 0xEC },\n    { address = 0x82833088, size = 0xB8 },\n    { address = 0x82A976D0, size = 0x198 },\n    { address = 0x829124F8, size = 0x100 },\n    { address = 0x828A06B0, size = 0x114 },\n    { address = 0x828C4B00, size = 0x1E4 },\n    { address = 0x82333F80, size = 0xE8 },\n    { address = 0x823D4768, size = 0x23C },\n    { address = 0x821EE1C8, size = 0x5C },\n    { address = 0x821EEAA8, size = 0xFC },\n    { address = 0x825B4110, size = 0x5C },\n    { address = 0x824C4498, size = 0xB4 },\n    { address = 0x82217FC0, size = 0xA8 },\n    { address = 0x827D9040, size = 0x380 },\n    { address = 0x82A4B870, size = 0x78 },\n    { address = 0x82A97868, size = 0x16C },\n    { address = 0x82912728, size = 0x128 },\n    { address = 0x82655978, size = 0x26C },\n    { address = 0x82547890, size = 0x234 },\n    { address = 0x8273C9B8, size = 0x364 },\n    { address = 0x8223F138, size = 0xCC },\n    { address = 0x82860FD0, size = 0x1A8 },\n    { address = 0x829DA628, size = 0x70 },\n    { address = 0x8223DD38, size = 0x1C4 },\n    { address = 0x824ABA68, size = 0xE0 },\n    { address = 0x824ABB10, size = 0x38 }\n]\n\ninvalid_instructions = [\n    { data = 0x00000000, size = 4 }, # Padding\n    { data = 0x826E3E60, size = 8 }, # C++ Frame Handler\n    { data = 0x82AEA1CC, size = 8 } # C Specific Frame Handler\n    # { data = 0x00485645, size = 44 } # End of .text\n]\n\n[[midasm_hook]]\nname = \"SetMSAALevel\"\naddress = 0x82607CD4\nregisters = [\"r4\"]\n\n[[midasm_hook]]\nname = \"TitleTask_SetDefaultOption\"\naddress = 0x82512B10\nregisters = [\"r3\", \"r4\"]\n\n[[midasm_hook]]\nname = \"SetLifeBarAnimation\"\naddress = 0x824D804C\nregisters = [\"r3\", \"r4\", \"r5\", \"r31\"]\n\n[[midasm_hook]]\nname = \"SetLifeBarAnimation\"\naddress = 0x824D80A8\nregisters = [\"r3\", \"r4\", \"r5\", \"r31\"]\n\n[[midasm_hook]]\nname = \"SetLifeBarAnimation\"\naddress = 0x824D8F34\nregisters = [\"r3\", \"r4\", \"r5\", \"r31\"]\n\n[[midasm_hook]]\nname = \"SetRingBarIndex\"\naddress = 0x824DEB40\nregisters = [\"r5\", \"r31\"]\n\n[[midasm_hook]]\nname = \"PostureDisableEdgeGrabLeftover\"\naddress = 0x82200568\nregisters = [\"r31\"]\n\n[[midasm_hook]]\nname = \"BeginBlockGetName\"\naddress = 0x82607850\nregisters = [\"r3\"]\n\n[[midasm_hook]]\nname = \"PedestrianAnimationLOD\"\naddress = 0x824C2808\nregisters = [\"r29\"]\nafter_instruction = true\n\n[[midasm_hook]]\nname = \"MidairControlForMachSpeed\"\naddress = 0x82210380\njump_address_on_true = 0x82210388\n\n[[midasm_hook]]\nname = \"MidairControlForMachSpeed\"\naddress = 0x822103B4\njump_address_on_true = 0x822103BC\n\n[[midasm_hook]]\nname = \"MidairControlForSnowboards\"\naddress = 0x82214710\njump_address_on_true = 0x82214714\n\n[[midasm_hook]]\nname = \"MidairControlForSnowboards\"\naddress = 0x8221487C\njump_address_on_true = 0x82214880\n\n[[midasm_hook]]\nname = \"MidairControlForSnowboards\"\naddress = 0x822145B8\njump_address_on_true = 0x822145BC\n\n[[midasm_hook]]\nname = \"MidairControlForSnowboards\"\naddress = 0x8221486C\njump_address_on_true = 0x82214870\n\n[[midasm_hook]]\nname = \"GetRenderWorldFBO\"\naddress = 0x8260D6EC\nregisters = [\"r4\"]\n\n[[midasm_hook]]\nname = \"FurtherObjectShadows\"\naddress = 0x8260D7E0\nregisters = [\"r26\"]\n\n[[midasm_hook]]\nname = \"PlayerDebugMode_RegisterLuaSetup\"\naddress = 0x82195AD0\nregisters = [\"r5\", \"r27\"]\n\n[[midasm_hook]]\nname = \"PlayerDebugMode_RemapDebugExitButton\"\naddress = 0x82221CA8 \nregisters = [\"r30\"]\njump_address_on_true = 0x82221CAC\njump_address_on_false = 0x82221CD8\n\n[[midasm_hook]]\nname = \"DisableRadialBlur\"\naddress = 0x82653834\njump_address_on_true = 0x82653840\n\n[[midasm_hook]]\nname = \"DisableKingdomValleyMist\"\naddress = 0x82311E50\njump_address_on_true = 0x82311E54\n\n[[midasm_hook]]\nname = \"PostureControl_RotationSpeedFix\"\naddress = 0x82201C6C\nregisters = [\"f1\", \"r1\"]\n\n[[midasm_hook]]\nname = \"CriCueUpdateDeltaTimeFix\"\naddress = 0x8260F238\nregisters = [\"f13\"]\n\n[[midasm_hook]]\nname = \"PowerUpJingleDurationFix\"\naddress = 0x8216CC28\nregisters = [\"f31\"]\n\n# Overwrite\n[[midasm_hook]]\nname = \"SaveAlertThreeOptionRemoveDeviceSelect\"\naddress = 0x8238CCCC\nregisters = [\"r5\"]\n\n# No Save Data\n[[midasm_hook]]\nname = \"SaveAlertThreeOptionRemoveDeviceSelect\"\naddress = 0x8238CE34\nregisters = [\"r5\"]\n\n[[midasm_hook]]\nname = \"XmvPlayerLang\"\naddress = 0x8264D844\nregisters = [\"r11\"]\n\n[[midasm_hook]]\nname = \"CsbSbkLang\"\naddress = 0x826124B0\nregisters = [\"r8\"]\n\n[[midasm_hook]]\nname = \"CsbSbkLang\"\naddress = 0x826121BC\nregisters = [\"r8\"]\n\n[[midasm_hook]]\nname = \"CsbSbkLang\"\naddress = 0x821784F8\nregisters = [\"r8\"]\n\n[[midasm_hook]]\nname = \"MovieVoiceLang\"\naddress = 0x8264B4E4\nregisters = [\"r19\"]\n\n[[midasm_hook]]\nname = \"RenderCsdCastNodeMidAsmHook\"\naddress = 0x828C8FBC\nregisters = [\"r10\", \"r24\"]\n\n[[midasm_hook]]\nname = \"RenderCsdCastMidAsmHook\"\naddress = 0x828C8FFC\nregisters = [\"r4\"]\n\n# Redirect tagdisplay_2p to tagdisplay_1p\n[[midasm_hook]]\nname = \"Load2PDisplayMidAsmHook\"\naddress = 0x824DAE90\njump_address = 0x824DAE94\n\n# Redirect battledisplay_2p to battledisplay_1p\n[[midasm_hook]]\nname = \"Load2PDisplayMidAsmHook\"\naddress = 0x824DADE8\njump_address = 0x824DADEC\n\n[[midasm_hook]]\nname = \"TitleTask_RedirectStateTransitionToOutroAnim\"\naddress = 0x82512C90\nregisters = [\"r31\"]\njump_address_on_true = 0x82512C9C\n\n[[midasm_hook]]\nname = \"ControllableTeleportDash\"\naddress = 0x8221C49C\njump_address_on_true = 0x8221C4A0\n\n[[midasm_hook]]\nname = \"ControllableBoundAttack\"\naddress = 0x82217EE0\njump_address_on_true = 0x82217EE4\n\n[[midasm_hook]]\nname = \"ControllableBoundAttack\"\naddress = 0x82217F1C\njump_address_on_true = 0x82217F20\n\n[[midasm_hook]]\nname = \"ControllableBoundAttack2\"\naddress = 0x82217DC8\nregisters = [\"cr6\"]\njump_address_on_true = 0x82217DD8\njump_address_on_false = 0x82217DCC\n\n[[midasm_hook]]\nname = \"ControllableSpinkick\"\naddress = 0x821A2984\njump_address_on_true = 0x821A2988\n\n[[midasm_hook]]\nname = \"ControllableSpinkick\"\naddress = 0x821A7A04\njump_address_on_true = 0x821A7A08\n\n# Skip SPAABBTree initialisation for point light search\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258DBC4\njump_address = 0x8258DBE0\n\n# Skip SPAABBTree initialisation for point light search\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258DF60\njump_address = 0x8258DF7C\n\n# Skip SPAABBTree initialisation for point light search\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258E29C\njump_address = 0x8258E2B8\n\n# Skip SPAABBTree initialisation for point light search\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258E598\njump_address = 0x8258E5B4\n\n# Skip point light update optimisation\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258DC28\njump_address = 0x8258DC2C\n\n# Skip point light update optimisation\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258DFC4\njump_address = 0x8258DFC8\n\n# Skip point light update optimisation\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258E304\njump_address = 0x8258E308\n\n# Skip point light update optimisation\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x8258E600\njump_address = 0x8258E604\n\n[[midasm_hook]]\nname = \"TextEntityAlloc\"\naddress = 0x825EDB70\nregisters = [\"r3\"]\n\n[[midasm_hook]]\nname = \"TextEntityAlloc\"\naddress = 0x825EDCEC\nregisters = [\"r3\"]\n\n[[midasm_hook]]\nname = \"CameraImp_SetFOV\"\naddress = 0x82590980\nregisters = [\"f1\"]\n\n[[midasm_hook]]\nname = \"CameraImp_SetFOV\"\naddress = 0x82590AB4\nregisters = [\"f1\"]\n\n[[midasm_hook]]\nname = \"SonicCamera_InvertAzDriveK\"\naddress = 0x82191D40\nregisters = [\"f12\"]\n\n[[midasm_hook]]\nname = \"SonicCamera_InvertAltDriveK\"\naddress = 0x82191E00\nregisters = [\"f12\"]\n\n[[midasm_hook]]\nname = \"SonicCamera_RotationSpeedFix\"\naddress = 0x82191DA0\nregisters = [\"f0\", \"f31\", \"f13\"]\njump_address = 0x82191DA4\n\n[[midasm_hook]]\nname = \"SonicCamera_RotationSpeedFix\"\naddress = 0x82191E28\nregisters = [\"f0\", \"f31\", \"f13\"]\njump_address = 0x82191E2C\n\n[[midasm_hook]]\nname = \"RestoreChaosSpearFlips\"\naddress = 0x821A6730\njump_address_on_true = 0x821A6734\n\n[[midasm_hook]]\nname = \"RestoreChaosBoostJump\"\naddress = 0x821A6988\nregisters = [\"r10\", \"r11\"]\n\n[[midasm_hook]]\nname = \"RestoreChainJumpFlips\"\naddress = 0x82199654\nregisters = [\"r31\", \"r30\", \"r11\", \"f1\", \"f2\", \"f3\"]\n\n[[midasm_hook]]\nname = \"DisablePushState\"\naddress = 0x821A03F0\njump_address_on_true = 0x821A03FC\n\n[[midasm_hook]]\nname = \"ObjTarzan_VolatileBranch\"\naddress = 0x8232D3E0\nregisters = [\"r30\"]\njump_address_on_true = 0x8232D3E4\n\n[[midasm_hook]]\nname = \"ObjTarzan_PatchStaticDeltaTime\"\naddress = 0x8232D3E4\nregisters = [\"f0\", \"r30\"]\nafter_instruction = true\n\n[[midasm_hook]]\nname = \"ObjTarzan_PatchStaticDeltaTime\"\naddress = 0x8232D49C\nregisters = [\"f1\", \"f3\"]\nafter_instruction = true\n\n[[midasm_hook]]\nname = \"ObjTarzan_PatchStaticDeltaTime\"\naddress = 0x8232D530\nregisters = [\"f1\", \"f3\"]\nafter_instruction = true\n\n[[midasm_hook]]\nname = \"ObjTarzan_PatchStaticDeltaTime\"\naddress = 0x8232D57C\nregisters = [\"f0\", \"f3\"]\nafter_instruction = true\n\n[[midasm_hook]]\nname = \"ObjTarzan_PatchDeltaTimeArgument\"\naddress = 0x8232D818\nregisters = [\"f3\", \"r4\", \"r1\"]\n\n[[midasm_hook]]\nname = \"ObjEspSwing_DecayRateFix\"\naddress = 0x8232A4D0\nregisters = [\"f0\", \"f13\", \"f28\"]\njump_address = 0x8232A4D4\n\n[[midasm_hook]]\nname = \"ObjectInputWarp_ExtendMsgSuckPlayer\"\naddress = 0x82389D70\nregisters = [\"r3\", \"r4\", \"f25\"]\njump_address = 0x82389D74\n\n[[midasm_hook]]\nname = \"ObjectInputWarp_ExtendMsgSuckPlayer\"\naddress = 0x82389E48\nregisters = [\"r3\", \"r4\", \"f25\"]\njump_address = 0x82389E4C\n\n[[midasm_hook]]\nname = \"PlayerObject_ProcessMsgSuckPlayer_FixForce\"\naddress = 0x8219B64C\nregisters = [\"r28\", \"f1\"]\n\n[[midasm_hook]]\nname = \"PlayerObject_ProcessMsgSuckPlayer_FixDeltaTime\"\naddress = 0x8219B660\nregisters = [\"r28\", \"f31\"]\n\n# Sonicteam::MyPE::CManageParticle::ManageParticleTask\n[[midasm_hook]]\nname = \"NOP\"\naddress = 0x82645518\njump_address = 0x8264551C\n\n# Sonicteam::MyPE::MyEmitter / Sonicteam::GE1PE::Emitter\n[[midasm_hook]]\nname = \"Spanverse_GE1PE_DeltaTimeFix\"\naddress = 0x82670728\nregisters = [\"f1\"]\n\n# Sonicteam::Spanverse::SpangleParticle\n[[midasm_hook]]\nname = \"Spanverse_GE1PE_DeltaTimeFix\"\naddress = 0x82674030\nregisters = [\"f13\"]\n\n# Sonicteam::Spanverse::SpangleLight\n[[midasm_hook]]\nname = \"Spanverse_GE1PE_DeltaTimeFix\"\naddress = 0x82674800\nregisters = [\"f13\"]\n\n[[midasm_hook]]\nname = \"HUDLoadingAlloc\"\naddress = 0x824FAE14\nregisters = [\"r3\"]\n\n[[midasm_hook]]\nname = \"HUDLoadingAlloc\"\naddress = 0x824FAF60\nregisters = [\"r3\"]\n\n[[midasm_hook]]\nname = \"HUDLoadingAlloc\"\naddress = 0x824FB008\nregisters = [\"r3\"]\n\n[[midasm_hook]]\nname = \"HUDLoading_DeltaTimeFix\"\naddress = 0x824D7510\nregisters = [\"r31\", \"f1\"]\n\n[[midasm_hook]]\nname = \"HUDWindow_PreserveDeltaTime\"\naddress = 0x824FA03C\nregisters = [\"f31\", \"f1\"]\n\n[[midasm_hook]]\nname = \"HUDWindow_Callback\"\naddress = 0x824FA088\nregisters = [\"f1\", \"f31\"]\n\n[[midasm_hook]]\nname = \"ObjectVehicleBike_EnemyShot_DisableVehicleCollisionLayer\"\naddress = 0x822ADBC4\nregisters = [\"r31\"]\njump_address_on_true = 0x822ADBC8\n\n[[midasm_hook]]\nname = \"SonicGauge_FixFlags\"\naddress = 0x822196EC\nregisters = [\"r3\", \"r31\"]\n\n[[midasm_hook]]\nname = \"SonicGauge_FixGemSprite\"\naddress = 0x82185768\nregisters = [\"r30\"]\n\n[[midasm_hook]]\nname = \"SonicGauge_FixGemSprite\"\naddress = 0x821855A4\nregisters = [\"r29\"]\n\n[[midasm_hook]]\nname = \"SonicGauge_FixGemSprite\"\naddress = 0x82177DFC\nregisters = [\"r31\"]\n\n[[midasm_hook]]\nname = \"GameImp_PauseMenu_AddQuitPrefix\"\naddress = 0x8217DF60\nregisters = [\"r1\", \"r30\"]\n\n# Sonicteam::Player::State::SonicFall\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82216B4C\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicJump\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82216CC8\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicWideSpring\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82216F14\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicHomingAfter\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x8221745C\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicBound\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82217CD4\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicWait\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82218B24\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicWalk\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82218DF8\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicRun\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x822190D8\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::FastRun\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x82211660\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowWait\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A5504\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowWalk\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A57C8\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowRun\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A5A90\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowFall\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A5D58\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowJump\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A5F60\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowWideSpring\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A6270\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::ShadowHomingAfter / Sonicteam::Player::State::ShadowChaosSpearAfter\n[[midasm_hook]]\nname = \"RemapLightDash\"\naddress = 0x821A6794\nregisters = [\"r3\", \"r11\"]\n\n# Sonicteam::Player::State::SonicWalk\n[[midasm_hook]]\nname = \"RemapAntigravityEnter\"\naddress = 0x82218E5C\nregisters = [\"r11\", \"r28\"]\n\n# Sonicteam::Player::State::SonicRun\n[[midasm_hook]]\nname = \"RemapAntigravityEnter\"\naddress = 0x8221913C\nregisters = [\"r11\", \"r28\"]\n\n# Sonicteam::Player::State::SonicSliding\n[[midasm_hook]]\nname = \"RemapAntigravityExit\"\naddress = 0x82217AB0\nregisters = [\"r11\", \"r30\"]\n\n[[midasm_hook]]\nname = \"AntigravityRetainsMomentum\"\naddress = 0x82217968\njump_address_on_true = 0x8221796C\n\n[[midasm_hook]]\nname = \"AntigravityRetainsMomentum\"\naddress = 0x82217970\njump_address_on_true = 0x82217974\n\n[[midasm_hook]]\nname = \"UnlimitedAntigravity\"\naddress = 0x82217A94\njump_address_on_true = 0x82217A98\n\n[[midasm_hook]]\nname = \"DemoGMCamera_InvertHorizontal\"\naddress = 0x8218CC64\nregisters = [\"f0\"]\n\n[[midasm_hook]]\nname = \"DemoGMCamera_InvertVertical\"\naddress = 0x8218CC6C\nregisters = [\"f0\"]\n\n# Sonicteam::Player::Sound::SuperSonic\n[[midasm_hook]]\nname = \"Super3_DisableChangeRequestHint\"\naddress = 0x8226B8EC\njump_address_on_true = 0x8226B910\n\n# Sonicteam::Player::Sound::SuperShadow / Sonicteam::Player::Sound::SuperSilver\n[[midasm_hook]]\nname = \"Super3_DisableChangeRequestHint\"\naddress = 0x8226AB4C\njump_address_on_true = 0x8226AB70\n\n[[midasm_hook]]\nname = \"InfiniteLives\"\naddress = 0x821857B0\njump_address_on_true = 0x821857B4\n\n[[midasm_hook]]\nname = \"UnlockAchievement\"\naddress = 0x825B0960\nregisters = [\"r29\"]\n\n[[midasm_hook]]\nname = \"HUDGoldMedal_ShouldDestroyTable\"\naddress = 0x824D6720\njump_address_on_false = 0x824D6724\n\n[[midasm_hook]]\nname = \"MainMenuTask_GoldMedalResults_SkipOutro\"\naddress = 0x82502E14\njump_address_on_true = 0x82502E90\n"
  },
  {
    "path": "MarathonRecompLib/config/switch_table.toml",
    "content": "# Generated by XenonAnalyse\n# ---- MANUAL JUMPTABLE ----\n[[switch]]\nbase = 0x826DC2EC\nr = 10\ndefault = 0x826dc314\nlabels = [\n    0x826dc314, #0\n    0x826dc48c, #1\n    0x826dc528, #2\n    0x826dc614, #3\n    0x826dc638, #4\n    0x826dc6f0, #5\n    0x826dc81c, #6\n    0x826dc8d0, #7\n    0x826dc988, #8\n    0x826dc9c8, #9\n    0x826dc9f4, #10\n    0x826dc9f4, #11\n    0x826dca00, #12\n    0x826dcac8, #13\n    0x826dcb30, #14\n    0x826dcbac, #15\n    0x826dcc74, #16\n    0x826dcf44, #17\n    0x826dd01c, #18\n    0x826dd1d8, #19\n    0x826dd240, #20\n    0x826dd388, #21\n    0x826dd410, #22\n    0x826dd4bc, #23\n    0x826dd4d8, #24\n    0x826dd5b4, #25\n    0x826dd654, #26\n    0x826dd65c, #27\n    0x826dd7b0, #28\n]\n# ---- ABSOLUTE JUMPTABLE ----\n[[switch]]\nbase = 0x821677E0\nr = 4\ndefault = 0x82167844\nlabels = [\n    0x82167844,\n    0x82167814,\n    0x8216782C,\n    0x8216781C,\n    0x82167824,\n    0x8216783C,\n    0x82167834,\n]\n\n[[switch]]\nbase = 0x82169220\nr = 11\ndefault = 0x82169288\nlabels = [\n    0x82169248,\n    0x82169254,\n    0x82169260,\n    0x8216926C,\n]\n\n[[switch]]\nbase = 0x82170F48\nr = 11\ndefault = 0x82170F24\nlabels = [\n    0x82170F74,\n    0x82170F98,\n    0x8217113C,\n    0x82170F98,\n    0x82171170,\n]\n\n[[switch]]\nbase = 0x82171620\nr = 29\ndefault = 0x82171778\nlabels = [\n    0x821716BC,\n    0x821716BC,\n    0x821716BC,\n    0x821716BC,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x82171778,\n    0x821716BC,\n    0x821716BC,\n]\n\n[[switch]]\nbase = 0x8217BE28\nr = 10\ndefault = 0x8217C5D0\nlabels = [\n    0x8217BE60,\n    0x8217BE78,\n    0x8217BE90,\n    0x8217BEA8,\n    0x8217BEC0,\n    0x8217BF08,\n    0x8217BED8,\n    0x8217BEF0,\n]\n\n[[switch]]\nbase = 0x8217BF4C\nr = 10\ndefault = 0x8217C5D0\nlabels = [\n    0x8217C148,\n    0x8217C5D0,\n    0x8217C12C,\n    0x8217BFDC,\n    0x8217C5D0,\n    0x8217BFF4,\n    0x8217C0B4,\n    0x8217C0CC,\n    0x8217C024,\n    0x8217C03C,\n    0x8217C00C,\n    0x8217C5D0,\n    0x8217C5D0,\n    0x8217C5D0,\n    0x8217C5D0,\n    0x8217C084,\n    0x8217C09C,\n    0x8217C0E4,\n    0x8217C0FC,\n    0x8217C114,\n    0x8217C054,\n    0x8217C06C,\n    0x8217BFC0,\n]\n\n[[switch]]\nbase = 0x8217C19C\nr = 10\ndefault = 0x8217C5D0\nlabels = [\n    0x8217C274,\n    0x8217C28C,\n    0x8217C2A4,\n    0x8217C2BC,\n    0x8217C2D4,\n    0x8217C308,\n    0x8217C344,\n    0x8217C35C,\n    0x8217C5D0,\n    0x8217C374,\n    0x8217C38C,\n    0x8217BBF4,\n    0x8217C5D0,\n    0x8217C5D0,\n    0x8217C234,\n    0x8217C24C,\n    0x8217C3BC,\n    0x8217C3A4,\n    0x8217C47C,\n    0x8217C5D0,\n    0x8217C494,\n    0x8217C4AC,\n    0x8217C4C4,\n    0x8217C3D4,\n    0x8217C3FC,\n    0x8217C424,\n    0x8217C44C,\n    0x8217C464,\n    0x8217C5D0,\n    0x8217C5D0,\n    0x8217C4DC,\n    0x8217C4F4,\n]\n\n[[switch]]\nbase = 0x82184C68\nr = 11\ndefault = 0x82184D10\nlabels = [\n    0x82184C90,\n    0x82184CB0,\n    0x82184CD0,\n    0x82184CD0,\n]\n\n[[switch]]\nbase = 0x82184F54\nr = 11\ndefault = 0x821850B4\nlabels = [\n    0x82184F88,\n    0x82185078,\n    0x8218506C,\n    0x82184FEC,\n    0x82185028,\n    0x82185084,\n    0x82185090,\n]\n\n[[switch]]\nbase = 0x82184F94\nr = 11\ndefault = 0x82184FE0\nlabels = [\n    0x82184FC8,\n    0x82184FC8,\n    0x821850B4,\n    0x82184FE0,\n    0x82184FC8,\n    0x82184FC8,\n    0x821850B4,\n]\n\n[[switch]]\nbase = 0x82184FF8\nr = 11\ndefault = 0x82184FE0\nlabels = [\n    0x82184FC8,\n    0x82184FC8,\n    0x82184FE0,\n    0x82184FE0,\n    0x82184FC8,\n    0x82184FC8,\n]\n\n[[switch]]\nbase = 0x821850C0\nr = 11\ndefault = 0x821851F8\nlabels = [\n    0x821850FC,\n    0x82185154,\n    0x82185164,\n    0x82185174,\n    0x8218519C,\n    0x821851AC,\n    0x821851BC,\n    0x821851CC,\n    0x821851DC,\n]\n\n[[switch]]\nbase = 0x82185104\nr = 28\ndefault = 0x82185144\nlabels = [\n    0x8218513C,\n    0x82185144,\n    0x8218513C,\n    0x8218513C,\n    0x82185144,\n    0x8218513C,\n    0x8218513C,\n    0x8218513C,\n]\n\n[[switch]]\nbase = 0x82185E00\nr = 11\ndefault = 0x82185EB4\nlabels = [\n    0x82185E38,\n    0x82185E78,\n    0x82185E48,\n    0x82185E58,\n    0x82185E68,\n    0x82185E88,\n    0x82185E98,\n    0x82185EA8,\n]\n\n[[switch]]\nbase = 0x82186550\nr = 5\ndefault = 0x82186640\nlabels = [\n    0x82186594,\n    0x821865BC,\n    0x821865E8,\n    0x82186610,\n    0x8218662C,\n    0x8218663C,\n    0x8218665C,\n    0x82186678,\n    0x82186640,\n    0x82186690,\n    0x8218663C,\n]\n\n[[switch]]\nbase = 0x8218761C\nr = 5\ndefault = 0x821877D8\nlabels = [\n    0x82187660,\n    0x82187680,\n    0x82187698,\n    0x821876B0,\n    0x821876C8,\n    0x8218771C,\n    0x82187744,\n    0x82187760,\n    0x821877D8,\n    0x8218777C,\n    0x821877A8,\n]\n\n[[switch]]\nbase = 0x82188684\nr = 11\ndefault = 0x82188898\nlabels = [\n    0x821886AC,\n    0x82188728,\n    0x821887A4,\n    0x82188820,\n]\n\n[[switch]]\nbase = 0x82189900\nr = 11\ndefault = 0x82189964\nlabels = [\n    0x82189934,\n    0x8218981C,\n    0x8218981C,\n    0x82189944,\n    0x8218981C,\n    0x82189964,\n    0x82189954,\n]\n\n[[switch]]\nbase = 0x821975D4\nr = 9\ndefault = 0x82198310\nlabels = [\n    0x82197690,\n    0x82198310,\n    0x82197658,\n    0x82197620,\n    0x8219763C,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x821976C8,\n    0x821976AC,\n    0x82198310,\n    0x82198310,\n    0x82197674,\n]\n\n[[switch]]\nbase = 0x82197708\nr = 9\ndefault = 0x82198310\nlabels = [\n    0x82197744,\n    0x82197760,\n    0x82198310,\n    0x82198310,\n    0x8219777C,\n    0x82197798,\n    0x82198310,\n    0x82198310,\n    0x821977B4,\n]\n\n[[switch]]\nbase = 0x82197810\nr = 9\ndefault = 0x82198310\nlabels = [\n    0x82197C24,\n    0x821979EC,\n    0x82197AB0,\n    0x82197ACC,\n    0x82197B3C,\n    0x82197B5C,\n    0x82197B20,\n    0x82197CD8,\n    0x82197CA0,\n    0x82198310,\n    0x82197974,\n    0x82197990,\n    0x82197D2C,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82197A08,\n    0x82197A5C,\n    0x82197A78,\n    0x82197A94,\n    0x82197A40,\n    0x82197A78,\n    0x82197A24,\n    0x82198310,\n    0x82197CBC,\n    0x82198310,\n    0x821979AC,\n    0x821979AC,\n    0x821979CC,\n    0x821979CC,\n    0x82198310,\n    0x82198310,\n    0x82197B98,\n    0x82198310,\n    0x82197BB4,\n    0x82198310,\n    0x82198310,\n    0x82197C44,\n    0x82197C68,\n    0x82197C84,\n    0x82197C68,\n    0x82198310,\n    0x82198310,\n    0x82197BD0,\n    0x82197BEC,\n    0x82197C08,\n    0x82197B7C,\n    0x82198310,\n    0x82198310,\n    0x82197D88,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82197D48,\n    0x82197D6C,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82198310,\n    0x82197CF4,\n    0x82198310,\n    0x82198310,\n    0x82197D10,\n    0x82197AE8,\n    0x82197B04,\n]\n\n[[switch]]\nbase = 0x82197DE4\nr = 9\ndefault = 0x82198310\nlabels = [\n    0x82197E84,\n    0x82197EA0,\n    0x82197EBC,\n    0x82197ED8,\n    0x82197EF4,\n    0x82197F34,\n    0x82197F18,\n    0x82197F50,\n    0x82197F70,\n    0x82197F90,\n    0x82197FAC,\n    0x82197FC8,\n    0x82198008,\n    0x82197FE8,\n    0x82198024,\n    0x82198040,\n    0x8219809C,\n    0x821980B8,\n    0x821980D4,\n    0x821980F8,\n    0x82198124,\n    0x82198140,\n    0x8219815C,\n    0x82198178,\n    0x821981A8,\n    0x821981C4,\n    0x82198228,\n    0x8219820C,\n    0x82198244,\n    0x82198260,\n    0x8219827C,\n    0x8219805C,\n    0x82198080,\n    0x82198310,\n]\n\n[[switch]]\nbase = 0x82198890\nr = 9\ndefault = 0x82198B18\nlabels = [\n    0x821988C8,\n    0x82198968,\n    0x82198A08,\n    0x82198B18,\n    0x82198B00,\n    0x82198AA8,\n    0x82198B08,\n    0x82198B10,\n]\n\n[[switch]]\nbase = 0x8219BC40\nr = 11\ndefault = 0x8219BDB4\nlabels = [\n    0x8219BC70,\n    0x8219BCAC,\n    0x8219BCF4,\n    0x8219BDB4,\n    0x8219BD3C,\n    0x8219BD84,\n]\n\n[[switch]]\nbase = 0x8219BE40\nr = 11\ndefault = 0x8219C45C\nlabels = [\n    0x8219BE70,\n    0x8219BF6C,\n    0x8219C068,\n    0x8219C16C,\n    0x8219C268,\n    0x8219C364,\n]\n\n[[switch]]\nbase = 0x8219C4BC\nr = 11\ndefault = 0x8219C7EC\nlabels = [\n    0x8219C4E8,\n    0x8219C5B4,\n    0x8219C64C,\n    0x8219C7EC,\n    0x8219C718,\n]\n\n[[switch]]\nbase = 0x8219D72C\nr = 11\ndefault = 0x8219DD28\nlabels = [\n    0x8219D780,\n    0x8219D7E0,\n    0x8219D840,\n    0x8219D8A0,\n    0x8219D900,\n    0x8219D960,\n    0x8219D9C0,\n    0x8219DA20,\n    0x8219DA80,\n    0x8219DAE0,\n    0x8219DB40,\n    0x8219DBA0,\n    0x8219DC00,\n    0x8219DC60,\n    0x8219DCC0,\n]\n\n[[switch]]\nbase = 0x8219DEF4\nr = 11\ndefault = 0x8219E3D4\nlabels = [\n    0x8219DF54,\n    0x8219DF94,\n    0x8219DFD4,\n    0x8219E014,\n    0x8219E054,\n    0x8219E094,\n    0x8219E0D4,\n    0x8219E114,\n    0x8219E154,\n    0x8219E194,\n    0x8219E1D4,\n    0x8219E214,\n    0x8219E254,\n    0x8219E298,\n    0x8219E2DC,\n    0x8219E320,\n    0x8219E360,\n    0x8219E3A0,\n]\n\n[[switch]]\nbase = 0x8219E6C8\nr = 11\ndefault = 0x8219E9B0\nlabels = [\n    0x8219E718,\n    0x8219E74C,\n    0x8219E780,\n    0x8219E7B4,\n    0x8219E7E8,\n    0x8219E81C,\n    0x8219E850,\n    0x8219E884,\n    0x8219E8B8,\n    0x8219E8EC,\n    0x8219E920,\n    0x8219E9B0,\n    0x8219E954,\n    0x8219E988,\n]\n\n[[switch]]\nbase = 0x8219EA50\nr = 11\ndefault = 0x8219EF7C\nlabels = [\n    0x8219EA90,\n    0x8219EB14,\n    0x8219EB98,\n    0x8219EC1C,\n    0x8219EC94,\n    0x8219EDA4,\n    0x8219EDF8,\n    0x8219ED1C,\n    0x8219EE70,\n    0x8219EEF0,\n]\n\n[[switch]]\nbase = 0x8219F008\nr = 11\ndefault = 0x8219F1D8\nlabels = [\n    0x8219F038,\n    0x8219F06C,\n    0x8219F0A0,\n    0x8219F0D4,\n    0x8219F108,\n    0x8219F16C,\n]\n\n[[switch]]\nbase = 0x8219F4C0\nr = 11\ndefault = 0x8219F68C\nlabels = [\n    0x8219F4E8,\n    0x8219F544,\n    0x8219F5A0,\n    0x8219F5FC,\n]\n\n[[switch]]\nbase = 0x821A3C74\nr = 10\ndefault = 0x821A3E20\nlabels = [\n    0x821A3E08,\n    0x821A3E34,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E20,\n    0x821A3E4C,\n    0x821A3E70,\n]\n\n[[switch]]\nbase = 0x821A43B0\nr = 11\ndefault = 0x821A4434\nlabels = [\n    0x821A43DC,\n    0x821A43DC,\n    0x821A4434,\n    0x821A4400,\n    0x821A441C,\n]\n\n[[switch]]\nbase = 0x821A6F60\nr = 11\ndefault = 0x821A6FA0\nlabels = [\n    0x821A6F88,\n    0x821A6FB8,\n    0x821A6FE4,\n    0x821A6FFC,\n]\n\n[[switch]]\nbase = 0x821B1894\nr = 11\ndefault = 0x821B195C\nlabels = [\n    0x821B18BC,\n    0x821B18BC,\n    0x821B18C4,\n    0x821B18DC,\n]\n\n[[switch]]\nbase = 0x821B1C10\nr = 11\ndefault = 0x821B1CCC\nlabels = [\n    0x821B1C38,\n    0x821B1C50,\n    0x821B1CBC,\n    0x821B1C50,\n]\n\n[[switch]]\nbase = 0x821B2848\nr = 11\ndefault = 0x821B2998\nlabels = [\n    0x821B2A78,\n    0x821B2954,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B2998,\n    0x821B29B4,\n    0x821B2998,\n    0x821B2A58,\n]\n\n[[switch]]\nbase = 0x821B2D78\nr = 11\ndefault = 0x821B3004\nlabels = [\n    0x821B3088,\n    0x821B2FCC,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3004,\n    0x821B3068,\n    0x821B3004,\n    0x821B301C,\n    0x821B3034,\n]\n\n[[switch]]\nbase = 0x821B39CC\nr = 11\ndefault = 0x821B3A14\nlabels = [\n    0x821B3A8C,\n    0x821B3AA4,\n    0x821B3AB0,\n    0x821B39FC,\n    0x821B3A2C,\n    0x821B3A40,\n]\n\n[[switch]]\nbase = 0x821B4100\nr = 11\ndefault = 0x821B4144\nlabels = [\n    0x821B412C,\n    0x821B415C,\n    0x821B4180,\n    0x821B4144,\n    0x821B41A8,\n]\n\n[[switch]]\nbase = 0x821B42F0\nr = 11\ndefault = 0x821B4380\nlabels = [\n    0x821B4368,\n    0x821B4398,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B4380,\n    0x821B43BC,\n    0x821B4380,\n    0x821B4380,\n    0x821B43E4,\n]\n\n[[switch]]\nbase = 0x821B4C6C\nr = 11\ndefault = 0x821B4CB4\nlabels = [\n    0x821B4D2C,\n    0x821B4D44,\n    0x821B4D94,\n    0x821B4C9C,\n    0x821B4CCC,\n    0x821B4CE0,\n]\n\n[[switch]]\nbase = 0x821B4F34\nr = 10\ndefault = 0x821B501C\nlabels = [\n    0x821B4FCC,\n    0x821B4FF8,\n    0x821B501C,\n    0x821B501C,\n    0x821B501C,\n    0x821B501C,\n    0x821B501C,\n    0x821B501C,\n    0x821B4F74,\n    0x821B4FA0,\n]\n\n[[switch]]\nbase = 0x821B53EC\nr = 11\ndefault = 0x821B5538\nlabels = [\n    0x821B566C,\n    0x821B54F8,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B5538,\n    0x821B55A8,\n    0x821B5558,\n    0x821B5650,\n]\n\n[[switch]]\nbase = 0x821E7160\nr = 3\ndefault = 0x821E71AC\nlabels = [\n    0x821E71AC,\n    0x821E718C,\n    0x821E7194,\n    0x821E719C,\n    0x821E71A4,\n]\n\n[[switch]]\nbase = 0x821E81C0\nr = 11\ndefault = 0x821E8290\nlabels = [\n    0x821E8290,\n    0x821E81EC,\n    0x821E81F8,\n    0x821E8204,\n    0x821E827C,\n]\n\n[[switch]]\nbase = 0x821EA7D0\nr = 3\ndefault = 0x821EACE8\nlabels = [\n    0x821EA800,\n    0x821EA85C,\n    0x821EA900,\n    0x821EA980,\n    0x821EAAFC,\n    0x821EAB7C,\n]\n\n[[switch]]\nbase = 0x821EE1D8\nr = 11\ndefault = 0x821EE21C\nlabels = [\n    0x821EE204,\n    0x821EE204,\n    0x821EE204,\n    0x821EE20C,\n    0x821EE204,\n]\n\n[[switch]]\nbase = 0x821EE798\nr = 11\ndefault = 0x821EE8DC\nlabels = [\n    0x821EE7C8,\n    0x821EE7E8,\n    0x821EE7F8,\n    0x821EE88C,\n    0x821EE864,\n    0x821EE89C,\n]\n\n[[switch]]\nbase = 0x821EEAC0\nr = 11\ndefault = 0x0\nlabels = [\n    0x821EEAEC,\n    0x821EEAFC,\n    0x821EEAEC,\n    0x821EEB3C,\n    0x821EEB74,\n]\n\n[[switch]]\nbase = 0x821F3C88\nr = 11\ndefault = 0x821F3D24\nlabels = [\n    0x821F3CB0,\n    0x821F3CB0,\n    0x821F3CD4,\n    0x821F3CD4,\n]\n\n[[switch]]\nbase = 0x821F465C\nr = 11\ndefault = 0x821F49AC\nlabels = [\n    0x821F4690,\n    0x821F46E4,\n    0x821F47C8,\n    0x821F47C8,\n    0x821F4878,\n    0x821F4718,\n    0x821F48FC,\n]\n\n[[switch]]\nbase = 0x821F8424\nr = 11\ndefault = 0x821F8478\nlabels = [\n    0x821F844C,\n    0x821F8484,\n    0x821F8530,\n    0x821F85B8,\n]\n\n[[switch]]\nbase = 0x821F86B8\nr = 11\ndefault = 0x821F87A0\nlabels = [\n    0x821F86E4,\n    0x821F8708,\n    0x821F8750,\n    0x821F8750,\n    0x821F8750,\n]\n\n[[switch]]\nbase = 0x821F9B30\nr = 11\ndefault = 0x821F9BD0\nlabels = [\n    0x821F9B5C,\n    0x821F9B5C,\n    0x821F9B80,\n    0x821F9B80,\n    0x821F9B5C,\n]\n\n[[switch]]\nbase = 0x821FDC34\nr = 11\ndefault = 0x821FDCB0\nlabels = [\n    0x821FDC70,\n    0x821FDC78,\n    0x821FDC80,\n    0x821FDC88,\n    0x821FDC90,\n    0x821FDC98,\n    0x821FDCB0,\n    0x821FDCA0,\n    0x821FDCA8,\n]\n\n[[switch]]\nbase = 0x82200700\nr = 25\ndefault = 0x82200890\nlabels = [\n    0x82200734,\n    0x82200854,\n    0x82200854,\n    0x82200854,\n    0x822007CC,\n    0x822007EC,\n    0x82200810,\n]\n\n[[switch]]\nbase = 0x82200910\nr = 25\ndefault = 0x82200E70\nlabels = [\n    0x82200950,\n    0x82200C58,\n    0x82200C58,\n    0x82200CE4,\n    0x82200C58,\n    0x82200D84,\n    0x82200E70,\n    0x82200E70,\n    0x82200E5C,\n    0x82200E5C,\n]\n\n[[switch]]\nbase = 0x822093A8\nr = 10\ndefault = 0x82209490\nlabels = [\n    0x82209430,\n    0x82209490,\n    0x82209454,\n    0x82209490,\n    0x82209490,\n    0x82209490,\n    0x82209490,\n    0x82209490,\n    0x822093E8,\n    0x8220940C,\n]\n\n[[switch]]\nbase = 0x82209A28\nr = 11\ndefault = 0x82209B28\nlabels = [\n    0x82209A50,\n    0x82209A90,\n    0x82209AD4,\n    0x82209B14,\n]\n\n[[switch]]\nbase = 0x8220B41C\nr = 10\ndefault = 0x8220B46C\nlabels = [\n    0x8220B4FC,\n    0x8220B51C,\n    0x8220B528,\n    0x8220B44C,\n    0x8220B498,\n    0x8220B4AC,\n]\n\n[[switch]]\nbase = 0x82211C64\nr = 11\ndefault = 0x82211CDC\nlabels = [\n    0x82211C90,\n    0x82211CA4,\n    0x82211D1C,\n    0x82211CDC,\n    0x82211D40,\n]\n\n[[switch]]\nbase = 0x82217FE8\nr = 11\ndefault = 0x82218060\nlabels = [\n    0x82218020,\n    0x82218028,\n    0x82218030,\n    0x82218038,\n    0x82218040,\n    0x82218048,\n    0x82218050,\n    0x82218058,\n]\n\n[[switch]]\nbase = 0x82218090\nr = 11\ndefault = 0x0\nlabels = [\n    0x822180C8,\n    0x822180D0,\n    0x822180D8,\n    0x822180E0,\n    0x822180E8,\n    0x822180F0,\n    0x822180F8,\n    0x82218100,\n]\n\n[[switch]]\nbase = 0x8221AB68\nr = 11\ndefault = 0x8221ABD0\nlabels = [\n    0x8221ABD8,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD8,\n    0x8221ABD8,\n    0x8221ABD0,\n    0x8221ABD8,\n    0x8221ABD0,\n    0x8221ABD8,\n    0x8221ABD0,\n    0x8221ABD8,\n    0x8221ABD8,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD0,\n    0x8221ABD8,\n]\n\n[[switch]]\nbase = 0x8221AF08\nr = 11\ndefault = 0x8221B008\nlabels = [\n    0x8221AF38,\n    0x8221AF58,\n    0x8221AF78,\n    0x8221AFA4,\n    0x8221AFC4,\n    0x8221AFF0,\n]\n\n[[switch]]\nbase = 0x8221B0A8\nr = 11\ndefault = 0x8221B188\nlabels = [\n    0x8221B0D8,\n    0x8221B0D8,\n    0x8221B0F8,\n    0x8221B124,\n    0x8221B144,\n    0x8221B170,\n]\n\n[[switch]]\nbase = 0x8221B6B8\nr = 11\ndefault = 0x8221B738\nlabels = [\n    0x8221B720,\n    0x8221B730,\n    0x8221B730,\n    0x8221B738,\n    0x8221B720,\n    0x8221B720,\n    0x8221B738,\n    0x8221B720,\n    0x8221B738,\n    0x8221B720,\n    0x8221B738,\n    0x8221B720,\n    0x8221B720,\n    0x8221B738,\n    0x8221B738,\n    0x8221B738,\n    0x8221B738,\n    0x8221B738,\n    0x8221B738,\n    0x8221B720,\n]\n\n[[switch]]\nbase = 0x8221C90C\nr = 10\ndefault = 0x8221C984\nlabels = [\n    0x8221C938,\n    0x8221C978,\n    0x8221C940,\n    0x8221C978,\n    0x8221C95C,\n]\n\n[[switch]]\nbase = 0x8221C994\nr = 11\ndefault = 0x8221CA4C\nlabels = [\n    0x8221C9C0,\n    0x8221CA6C,\n    0x8221CACC,\n    0x8221CAF8,\n    0x8221CB18,\n]\n\n[[switch]]\nbase = 0x8221CBB0\nr = 11\ndefault = 0x8221CCA8\nlabels = [\n    0x8221CBD8,\n    0x8221CC3C,\n    0x8221CC74,\n    0x8221CC8C,\n]\n\n[[switch]]\nbase = 0x8221D03C\nr = 11\ndefault = 0x8221D178\nlabels = [\n    0x8221D06C,\n    0x8221D088,\n    0x8221D0D8,\n    0x8221D114,\n    0x8221D15C,\n    0x8221D0F4,\n]\n\n[[switch]]\nbase = 0x82223640\nr = 11\ndefault = 0x822238B4\nlabels = [\n    0x82223810,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x822238B4,\n    0x8222384C,\n    0x82223888,\n    0x822238B4,\n    0x82223890,\n]\n\n[[switch]]\nbase = 0x82223E88\nr = 11\ndefault = 0x82223F6C\nlabels = [\n    0x82223F38,\n    0x82223F40,\n    0x82223F48,\n    0x82223EC0,\n    0x82223F30,\n    0x82223F6C,\n    0x82223F6C,\n    0x82223EC0,\n]\n\n[[switch]]\nbase = 0x82224318\nr = 11\ndefault = 0x82224744\nlabels = [\n    0x8222469C,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x822246D8,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x822246D8,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x82224744,\n    0x822246D8,\n    0x822246D8,\n]\n\n[[switch]]\nbase = 0x82224750\nr = 11\ndefault = 0x82224834\nlabels = [\n    0x822247A0,\n    0x822247A0,\n    0x822247A0,\n    0x82224834,\n    0x82224834,\n    0x82224834,\n    0x82224834,\n    0x82224834,\n    0x822247A0,\n    0x822247A0,\n    0x822247A0,\n    0x822247A0,\n    0x822247A0,\n    0x822247A0,\n]\n\n[[switch]]\nbase = 0x82226858\nr = 11\ndefault = 0x82226DF0\nlabels = [\n    0x82226B00,\n    0x82226B4C,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226D2C,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DCC,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226DF0,\n    0x82226B98,\n    0x82226B98,\n    0x82226BE4,\n    0x82226C30,\n    0x82226C7C,\n    0x82226CC8,\n    0x82226D14,\n    0x82226D1C,\n    0x82226D24,\n]\n\n[[switch]]\nbase = 0x82226E64\nr = 30\ndefault = 0x82226F9C\nlabels = [\n    0x82226E8C,\n    0x82226EA4,\n    0x82226EE8,\n    0x82226F2C,\n]\n\n[[switch]]\nbase = 0x82227F10\nr = 11\ndefault = 0x82228218\nlabels = [\n    0x82228098,\n    0x82227FA8,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x822280B8,\n    0x82228218,\n    0x82228134,\n    0x82228184,\n    0x82228048,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x82228218,\n    0x822280A8,\n]\n\n[[switch]]\nbase = 0x82228A58\nr = 11\ndefault = 0x82228DE4\nlabels = [\n    0x82228C08,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DA0,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228CFC,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228D64,\n    0x82228DE4,\n    0x82228DE4,\n    0x82228D28,\n]\n\n[[switch]]\nbase = 0x822292D4\nr = 11\ndefault = 0x82229444\nlabels = [\n    0x82229390,\n    0x822293CC,\n    0x82229318,\n    0x82229354,\n    0x82229444,\n    0x82229444,\n    0x82229444,\n    0x82229444,\n    0x82229444,\n    0x82229444,\n    0x82229408,\n]\n\n[[switch]]\nbase = 0x82229754\nr = 11\ndefault = 0x8222988C\nlabels = [\n    0x82229804,\n    0x82229848,\n    0x8222977C,\n    0x822297C0,\n]\n\n[[switch]]\nbase = 0x82229C5C\nr = 11\ndefault = 0x82229DF4\nlabels = [\n    0x82229D7C,\n    0x82229DB8,\n    0x82229D04,\n    0x82229D40,\n    0x82229C8C,\n    0x82229CC8,\n]\n\n[[switch]]\nbase = 0x8222A118\nr = 11\ndefault = 0x8222A508\nlabels = [\n    0x8222A3B4,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A44C,\n    0x8222A508,\n    0x8222A508,\n    0x8222A508,\n    0x8222A498,\n    0x8222A4E4,\n]\n\n[[switch]]\nbase = 0x8222A798\nr = 11\ndefault = 0x8222A7F0\nlabels = [\n    0x8222A7C4,\n    0x8222A7C4,\n    0x8222A7C4,\n    0x8222A7CC,\n    0x8222A7CC,\n]\n\n[[switch]]\nbase = 0x8222A7FC\nr = 11\ndefault = 0x8222A89C\nlabels = [\n    0x8222A860,\n    0x8222A860,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A89C,\n    0x8222A868,\n    0x8222A870,\n    0x8222A89C,\n    0x8222A878,\n    0x8222A89C,\n    0x8222A868,\n    0x8222A89C,\n    0x8222A870,\n    0x8222A878,\n]\n\n[[switch]]\nbase = 0x8222A8AC\nr = 11\ndefault = 0x8222A978\nlabels = [\n    0x8222A930,\n    0x8222A930,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A978,\n    0x8222A930,\n    0x8222A930,\n    0x8222A930,\n    0x8222A930,\n    0x8222A930,\n    0x8222A930,\n    0x8222A930,\n]\n\n[[switch]]\nbase = 0x82234F80\nr = 4\ndefault = 0x822352F0\nlabels = [\n    0x82234FB0,\n    0x82235068,\n    0x82235228,\n    0x822351B0,\n    0x822352A0,\n    0x822352CC,\n]\n\n[[switch]]\nbase = 0x822387E0\nr = 4\ndefault = 0x82238B7C\nlabels = [\n    0x82238814,\n    0x822388B4,\n    0x82238A58,\n    0x822389E4,\n    0x82238B1C,\n    0x82238B30,\n    0x82238B44,\n]\n\n[[switch]]\nbase = 0x8223DD44\nr = 11\ndefault = 0x8223DEF4\nlabels = [\n    0x8223DD88,\n    0x8223DDA8,\n    0x8223DDC8,\n    0x8223DDE8,\n    0x8223DE08,\n    0x8223DE28,\n    0x8223DE4C,\n    0x8223DE6C,\n    0x8223DE8C,\n    0x8223DEB0,\n    0x8223DED0,\n]\n\n[[switch]]\nbase = 0x8223DF0C\nr = 11\ndefault = 0x0\nlabels = [\n    0x8223DF50,\n    0x8223DF64,\n    0x8223DF78,\n    0x8223DF8C,\n    0x8223DFA0,\n    0x8223DFB4,\n    0x8223DFC8,\n    0x8223DFDC,\n    0x8223DFF0,\n    0x8223E004,\n    0x8223E030,\n]\n\n[[switch]]\nbase = 0x8223EFF4\nr = 11\ndefault = 0x8223F12C\nlabels = [\n    0x8223F02C,\n    0x8223F04C,\n    0x8223F06C,\n    0x8223F08C,\n    0x8223F0AC,\n    0x8223F0CC,\n    0x8223F0EC,\n    0x8223F10C,\n]\n\n[[switch]]\nbase = 0x8223F144\nr = 11\ndefault = 0x0\nlabels = [\n    0x8223F17C,\n    0x8223F184,\n    0x8223F1A4,\n    0x8223F1AC,\n    0x8223F1B4,\n    0x8223F1BC,\n    0x8223F1C4,\n    0x8223F1E4,\n]\n\n[[switch]]\nbase = 0x82265C2C\nr = 11\ndefault = 0x82265FD0\nlabels = [\n    0x82265F44,\n    0x82265FD0,\n    0x82265F5C,\n    0x82265D60,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265EAC,\n    0x82265FD0,\n    0x82265F2C,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265F14,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265F74,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FA4,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265FD0,\n    0x82265F8C,\n]\n\n[[switch]]\nbase = 0x82265D70\nr = 11\ndefault = 0x82265E94\nlabels = [\n    0x82265DEC,\n    0x82265E4C,\n    0x82265E04,\n    0x82265DD4,\n    0x82265DBC,\n    0x82265E94,\n    0x82265E1C,\n    0x82265E34,\n    0x82265E7C,\n    0x82265E94,\n    0x82265E94,\n    0x82265E94,\n    0x82265E64,\n]\n\n[[switch]]\nbase = 0x822662A0\nr = 11\ndefault = 0x822663E0\nlabels = [\n    0x822663C8,\n    0x82266320,\n    0x82266380,\n    0x82266338,\n    0x82266308,\n    0x822662F0,\n    0x822663E0,\n    0x82266350,\n    0x82266368,\n    0x822663B0,\n    0x822663E0,\n    0x822663E0,\n    0x822663E0,\n    0x82266398,\n]\n\n[[switch]]\nbase = 0x82266430\nr = 11\ndefault = 0x82266530\nlabels = [\n    0x822664A4,\n    0x822664F4,\n    0x822664B8,\n    0x82266490,\n    0x8226647C,\n    0x82266530,\n    0x822664CC,\n    0x822664E0,\n    0x8226651C,\n    0x82266530,\n    0x82266530,\n    0x82266530,\n    0x82266508,\n]\n\n[[switch]]\nbase = 0x82266990\nr = 11\ndefault = 0x82266C90\nlabels = [\n    0x82266BC8,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C90,\n    0x82266C04,\n    0x82266C4C,\n    0x82266C90,\n    0x82266B60,\n]\n\n[[switch]]\nbase = 0x82267054\nr = 11\ndefault = 0x82267300\nlabels = [\n    0x82267298,\n    0x82267300,\n    0x82267300,\n    0x82267298,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x82267300,\n    0x822672B0,\n    0x82267300,\n    0x822672E0,\n    0x82267300,\n    0x822672C8,\n]\n\n[[switch]]\nbase = 0x822675DC\nr = 11\ndefault = 0x822677D4\nlabels = [\n    0x8226776C,\n    0x822677D4,\n    0x822677D4,\n    0x8226776C,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x8226779C,\n    0x82267784,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677D4,\n    0x822677B4,\n]\n\n[[switch]]\nbase = 0x82267C44\nr = 11\ndefault = 0x82267E3C\nlabels = [\n    0x82267DEC,\n    0x82267E3C,\n    0x82267D8C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E04,\n    0x82267D8C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267DC8,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E3C,\n    0x82267E1C,\n]\n\n[[switch]]\nbase = 0x822681A0\nr = 11\ndefault = 0x8226838C\nlabels = [\n    0x822681E0,\n    0x8226838C,\n    0x82268204,\n    0x82268240,\n    0x8226827C,\n    0x82268330,\n    0x82268348,\n    0x822682B8,\n    0x8226838C,\n    0x822682F4,\n]\n\n[[switch]]\nbase = 0x82268684\nr = 11\ndefault = 0x82268A4C\nlabels = [\n    0x82268A08,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A08,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A08,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A4C,\n    0x82268A08,\n    0x82268A08,\n]\n\n[[switch]]\nbase = 0x82268C58\nr = 11\ndefault = 0x82268E10\nlabels = [\n    0x82268D0C,\n    0x82268E10,\n    0x82268E10,\n    0x82268D0C,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268E10,\n    0x82268D0C,\n    0x82268D48,\n    0x82268D90,\n    0x82268DCC,\n    0x82268E10,\n    0x82268CE8,\n]\n\n[[switch]]\nbase = 0x822691BC\nr = 11\ndefault = 0x82269564\nlabels = [\n    0x8226937C,\n    0x822694F0,\n    0x82269564,\n    0x822694D8,\n    0x822694D8,\n    0x822694D8,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269544,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x8226952C,\n    0x82269564,\n    0x822693D0,\n    0x82269564,\n    0x82269364,\n    0x82269564,\n    0x822693B8,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269564,\n    0x82269460,\n    0x82269460,\n    0x82269460,\n    0x82269460,\n    0x8226949C,\n    0x822693E8,\n    0x822693E8,\n    0x82269424,\n]\n\n[[switch]]\nbase = 0x82269768\nr = 11\ndefault = 0x82269810\nlabels = [\n    0x8226979C,\n    0x82269810,\n    0x8226979C,\n    0x82269810,\n    0x82269810,\n    0x822697D8,\n    0x822697F0,\n]\n\n[[switch]]\nbase = 0x82269818\nr = 29\ndefault = 0x82269888\nlabels = [\n    0x82269868,\n    0x82269870,\n    0x82269870,\n    0x82269878,\n    0x82269880,\n    0x82269880,\n    0x82269880,\n    0x82269880,\n    0x82269888,\n    0x82269888,\n    0x82269888,\n    0x82269888,\n    0x82269888,\n    0x82269868,\n]\n\n[[switch]]\nbase = 0x822698A4\nr = 11\ndefault = 0x82269984\nlabels = [\n    0x822698D0,\n    0x822698EC,\n    0x82269910,\n    0x82269934,\n    0x82269958,\n]\n\n[[switch]]\nbase = 0x82269DD0\nr = 11\ndefault = 0x8226A194\nlabels = [\n    0x8226A048,\n    0x8226A00C,\n    0x8226A194,\n    0x8226A108,\n    0x8226A108,\n    0x8226A108,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A084,\n    0x8226A194,\n    0x8226A0CC,\n    0x8226A0F0,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A120,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A194,\n    0x8226A144,\n    0x8226A168,\n]\n\n[[switch]]\nbase = 0x8226A614\nr = 11\ndefault = 0x8226AAB4\nlabels = [\n    0x8226AAA8,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226A9D8,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226A9A0,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226AAB4,\n    0x8226A958,\n    0x8226A97C,\n]\n\n[[switch]]\nbase = 0x8226A9EC\nr = 11\ndefault = 0x8226AAB4\nlabels = [\n    0x8226AA18,\n    0x8226AA18,\n    0x8226AA3C,\n    0x8226AA60,\n    0x8226AA84,\n]\n\n[[switch]]\nbase = 0x8226AD3C\nr = 11\ndefault = 0x8226B1EC\nlabels = [\n    0x8226B1E0,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B110,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B0D8,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B1EC,\n    0x8226B090,\n    0x8226B1EC,\n    0x8226B0B4,\n]\n\n[[switch]]\nbase = 0x8226B124\nr = 11\ndefault = 0x8226B1EC\nlabels = [\n    0x8226B150,\n    0x8226B150,\n    0x8226B174,\n    0x8226B198,\n    0x8226B1BC,\n]\n\n[[switch]]\nbase = 0x8226B358\nr = 11\ndefault = 0x8226B864\nlabels = [\n    0x8226B858,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B778,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B73C,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B864,\n    0x8226B68C,\n    0x8226B6E4,\n]\n\n[[switch]]\nbase = 0x8226B78C\nr = 11\ndefault = 0x8226B864\nlabels = [\n    0x8226B7B8,\n    0x8226B7B8,\n    0x8226B7E0,\n    0x8226B808,\n    0x8226B830,\n]\n\n[[switch]]\nbase = 0x8226BB48\nr = 11\ndefault = 0x8226BF44\nlabels = [\n    0x8226BE58,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BF44,\n    0x8226BE7C,\n    0x8226BEC4,\n    0x8226BF00,\n    0x8226BE04,\n    0x8226BF44,\n    0x8226BE1C,\n]\n\n[[switch]]\nbase = 0x8226C264\nr = 11\ndefault = 0x8226C2EC\nlabels = [\n    0x8226C290,\n    0x8226C2B4,\n    0x8226C290,\n    0x8226C2CC,\n    0x8226C2CC,\n]\n\n[[switch]]\nbase = 0x8226C2F8\nr = 11\ndefault = 0x8226C4B4\nlabels = [\n    0x8226C464,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C47C,\n    0x8226C494,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C3A4,\n    0x8226C3A4,\n    0x8226C428,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C3A4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C3A4,\n    0x8226C428,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C4B4,\n    0x8226C3A4,\n    0x8226C3A4,\n    0x8226C3EC,\n    0x8226C3EC,\n    0x8226C3EC,\n    0x8226C3EC,\n]\n\n[[switch]]\nbase = 0x8227E66C\nr = 3\ndefault = 0x8227E804\nlabels = [\n    0x8227E6BC,\n    0x8227E6D8,\n    0x8227E6F4,\n    0x8227E710,\n    0x8227E6D8,\n    0x8227E72C,\n    0x8227E748,\n    0x8227E764,\n    0x8227E780,\n    0x8227E79C,\n    0x8227E7B8,\n    0x8227E7D4,\n    0x8227E804,\n    0x8227E7F0,\n]\n\n[[switch]]\nbase = 0x8227EFE8\nr = 11\ndefault = 0x8227F184\nlabels = [\n    0x8227F168,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F184,\n    0x8227F114,\n    0x8227F130,\n    0x8227F14C,\n]\n\n[[switch]]\nbase = 0x822862F8\nr = 11\ndefault = 0x82286494\nlabels = [\n    0x82286478,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286494,\n    0x82286424,\n    0x82286440,\n    0x8228645C,\n]\n\n[[switch]]\nbase = 0x82287C50\nr = 10\ndefault = 0x82287D30\nlabels = [\n    0x82287CBC,\n    0x82287CAC,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287CF4,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287D30,\n    0x82287CCC,\n]\n\n[[switch]]\nbase = 0x822898B0\nr = 11\ndefault = 0x82289934\nlabels = [\n    0x822898F8,\n    0x822898E4,\n    0x82289934,\n    0x82289934,\n    0x82289934,\n    0x8228990C,\n    0x82289920,\n]\n\n[[switch]]\nbase = 0x8228CB08\nr = 11\ndefault = 0x8228CB58\nlabels = [\n    0x8228CB34,\n    0x8228CB3C,\n    0x8228CB3C,\n    0x8228CB44,\n    0x8228CB4C,\n]\n\n[[switch]]\nbase = 0x82292E80\nr = 11\ndefault = 0x82292F80\nlabels = [\n    0x82292F68,\n    0x82292F80,\n    0x82292F80,\n    0x82292F80,\n    0x82292F80,\n    0x82292F80,\n    0x82292F80,\n    0x82292ED0,\n    0x82292F3C,\n    0x82292F80,\n    0x82292EE4,\n    0x82292F24,\n    0x82292F80,\n    0x82292F54,\n]\n\n[[switch]]\nbase = 0x822930F0\nr = 11\ndefault = 0x822931A0\nlabels = [\n    0x8229311C,\n    0x82293188,\n    0x822931A0,\n    0x82293130,\n    0x82293170,\n]\n\n[[switch]]\nbase = 0x822A5CF4\nr = 11\ndefault = 0x822A5F24\nlabels = [\n    0x822A5D1C,\n    0x822A5D80,\n    0x822A5DE4,\n    0x822A5E48,\n]\n\n[[switch]]\nbase = 0x822A6228\nr = 10\ndefault = 0x822A74F8\nlabels = [\n    0x822A6250,\n    0x822A6744,\n    0x822A6C38,\n    0x822A7178,\n]\n\n[[switch]]\nbase = 0x822A753C\nr = 11\ndefault = 0x822A783C\nlabels = [\n    0x822A7564,\n    0x822A7654,\n    0x822A7704,\n    0x822A7794,\n]\n\n[[switch]]\nbase = 0x822A7570\nr = 11\ndefault = 0x822A783C\nlabels = [\n    0x822A75AC,\n    0x822A75AC,\n    0x822A75AC,\n    0x822A75F8,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A7824,\n    0x822A7644,\n]\n\n[[switch]]\nbase = 0x822A7660\nr = 11\ndefault = 0x822A783C\nlabels = [\n    0x822A76AC,\n    0x822A76AC,\n    0x822A76AC,\n    0x822A76D0,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A7824,\n    0x822A7644,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A76F4,\n]\n\n[[switch]]\nbase = 0x822A7710\nr = 11\ndefault = 0x822A783C\nlabels = [\n    0x822A76AC,\n    0x822A76AC,\n    0x822A76AC,\n    0x822A7784,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A7824,\n    0x822A7644,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A7774,\n    0x822A7764,\n]\n\n[[switch]]\nbase = 0x822A77A0\nr = 11\ndefault = 0x822A783C\nlabels = [\n    0x822A77DC,\n    0x822A783C,\n    0x822A783C,\n    0x822A783C,\n    0x822A7800,\n    0x822A783C,\n    0x822A783C,\n    0x822A7824,\n    0x822A7644,\n]\n\n[[switch]]\nbase = 0x822A8720\nr = 10\ndefault = 0x822AB160\nlabels = [\n    0x822A8748,\n    0x822A9214,\n    0x822A9DC0,\n    0x822AA828,\n]\n\n[[switch]]\nbase = 0x822A8768\nr = 11\ndefault = 0x822A91F8\nlabels = [\n    0x822A87AC,\n    0x822A87AC,\n    0x822A87AC,\n    0x822A87AC,\n    0x822A8BC8,\n    0x822A8CC4,\n    0x822A91F8,\n    0x822A8E8C,\n    0x822A8FC4,\n    0x822A91F8,\n    0x822A90FC,\n]\n\n[[switch]]\nbase = 0x822A9234\nr = 11\ndefault = 0x822A91F8\nlabels = [\n    0x822A9280,\n    0x822A9280,\n    0x822A9280,\n    0x822A94A8,\n    0x822A9688,\n    0x822A9784,\n    0x822A91F8,\n    0x822A994C,\n    0x822A9A84,\n    0x822A91F8,\n    0x822A9BBC,\n    0x822A91F8,\n    0x822A9CB8,\n]\n\n[[switch]]\nbase = 0x822A9DE0\nr = 11\ndefault = 0x822A91F8\nlabels = [\n    0x822A9F3C,\n    0x822A9F3C,\n    0x822A9F3C,\n    0x822AA1B0,\n    0x822AA2B8,\n    0x822A91F8,\n    0x822A91F8,\n    0x822AA3B4,\n    0x822AA4EC,\n    0x822A91F8,\n    0x822AA72C,\n    0x822A91F8,\n    0x822A91F8,\n    0x822AA624,\n    0x822A9E34,\n]\n\n[[switch]]\nbase = 0x822AA848\nr = 11\ndefault = 0x822A91F8\nlabels = [\n    0x822AA884,\n    0x822A91F8,\n    0x822A91F8,\n    0x822A91F8,\n    0x822AAAB0,\n    0x822AACDC,\n    0x822A91F8,\n    0x822AAEF0,\n    0x822AB028,\n]\n\n[[switch]]\nbase = 0x822AE744\nr = 11\ndefault = 0x822AE81C\nlabels = [\n    0x822AE774,\n    0x822AE790,\n    0x822AE7AC,\n    0x822AE5D0,\n    0x822AE800,\n    0x822AE7C8,\n]\n\n[[switch]]\nbase = 0x822B27B4\nr = 11\ndefault = 0x822B28AC\nlabels = [\n    0x822B27E8,\n    0x822B2804,\n    0x822B2820,\n    0x822B283C,\n    0x822B2640,\n    0x822B2890,\n    0x822B2858,\n]\n\n[[switch]]\nbase = 0x822B6DA0\nr = 11\ndefault = 0x822B6E84\nlabels = [\n    0x822B6DD0,\n    0x822B6DEC,\n    0x822B6E08,\n    0x822B6E84,\n    0x822B6E5C,\n    0x822B6E24,\n]\n\n[[switch]]\nbase = 0x822BC034\nr = 11\ndefault = 0x822BC10C\nlabels = [\n    0x822BC064,\n    0x822BC080,\n    0x822BC09C,\n    0x822BBEC0,\n    0x822BC0F0,\n    0x822BC0B8,\n]\n\n[[switch]]\nbase = 0x822C27E8\nr = 11\ndefault = 0x822C30AC\nlabels = [\n    0x822C2814,\n    0x822C28AC,\n    0x822C30AC,\n    0x822C29D8,\n    0x822C2EC4,\n]\n\n[[switch]]\nbase = 0x822C3668\nr = 11\ndefault = 0x822C36D4\nlabels = [\n    0x822C3690,\n    0x822C36A0,\n    0x822C36B0,\n    0x822C36C0,\n]\n\n[[switch]]\nbase = 0x822C8D44\nr = 11\ndefault = 0x822C8E84\nlabels = [\n    0x822C8D6C,\n    0x822C8DBC,\n    0x822C8DD8,\n    0x822C8E7C,\n]\n\n[[switch]]\nbase = 0x822CD01C\nr = 11\ndefault = 0x822CD274\nlabels = [\n    0x822CD048,\n    0x822CD07C,\n    0x822CD0B0,\n    0x822CD134,\n    0x822CD1F0,\n]\n\n[[switch]]\nbase = 0x822D6220\nr = 10\ndefault = 0x822D6268\nlabels = [\n    0x822D6268,\n    0x822D6248,\n    0x822D6254,\n    0x822D6260,\n]\n\n[[switch]]\nbase = 0x822D6488\nr = 11\ndefault = 0x822D6664\nlabels = [\n    0x822D64B0,\n    0x822D6550,\n    0x822D64B0,\n    0x822D65F0,\n]\n\n[[switch]]\nbase = 0x822D70BC\nr = 11\ndefault = 0x822D7140\nlabels = [\n    0x822D70E4,\n    0x822D710C,\n    0x822D70E4,\n    0x822D710C,\n]\n\n[[switch]]\nbase = 0x822D7644\nr = 11\ndefault = 0x822D7728\nlabels = [\n    0x822D766C,\n    0x822D76A8,\n    0x822D766C,\n    0x822D76E4,\n]\n\n[[switch]]\nbase = 0x822D800C\nr = 11\ndefault = 0x822D84E4\nlabels = [\n    0x822D8034,\n    0x822D8130,\n    0x822D82AC,\n    0x822D840C,\n]\n\n[[switch]]\nbase = 0x822DAB10\nr = 11\ndefault = 0x822DAE58\nlabels = [\n    0x822DAB38,\n    0x822DAE58,\n    0x822DAE58,\n    0x822DAE50,\n]\n\n[[switch]]\nbase = 0x822DABD8\nr = 11\ndefault = 0x822DAE58\nlabels = [\n    0x822DAE58,\n    0x822DAC00,\n    0x822DAC08,\n    0x822DAE58,\n]\n\n[[switch]]\nbase = 0x822DAC98\nr = 11\ndefault = 0x822DAE58\nlabels = [\n    0x822DACC0,\n    0x822DAE58,\n    0x822DAE58,\n    0x822DAE50,\n]\n\n[[switch]]\nbase = 0x822DADA4\nr = 11\ndefault = 0x822DAE58\nlabels = [\n    0x822DADCC,\n    0x822DAE58,\n    0x822DAE58,\n    0x822DAE50,\n]\n\n[[switch]]\nbase = 0x822DAFE0\nr = 11\ndefault = 0x822DB024\nlabels = [\n    0x822DB024,\n    0x822DB008,\n    0x822DB024,\n    0x822DB024,\n]\n\n[[switch]]\nbase = 0x822DD5E0\nr = 11\ndefault = 0x822DD6B4\nlabels = [\n    0x822DD64C,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD6A4,\n    0x822DD6B4,\n    0x822DD6B4,\n    0x822DD62C,\n    0x822DD638,\n]\n\n[[switch]]\nbase = 0x822E24C8\nr = 11\ndefault = 0x822E2640\nlabels = [\n    0x822E24F4,\n    0x822E2530,\n    0x822E257C,\n    0x822E25C0,\n    0x822E25FC,\n]\n\n[[switch]]\nbase = 0x822FB668\nr = 11\ndefault = 0x822FB724\nlabels = [\n    0x822FB690,\n    0x822FB6AC,\n    0x822FB6C8,\n    0x822FB714,\n]\n\n[[switch]]\nbase = 0x82300174\nr = 11\ndefault = 0x8230023C\nlabels = [\n    0x823001B0,\n    0x823001C0,\n    0x823001D0,\n    0x823001E0,\n    0x823001F0,\n    0x82300200,\n    0x82300210,\n    0x82300220,\n    0x82300230,\n]\n\n[[switch]]\nbase = 0x8230F0B4\nr = 11\ndefault = 0x8230F338\nlabels = [\n    0x8230F0DC,\n    0x8230F0F0,\n    0x8230F1C4,\n    0x8230F298,\n]\n\n[[switch]]\nbase = 0x82315E2C\nr = 11\ndefault = 0x82316304\nlabels = [\n    0x82315E58,\n    0x82315EA4,\n    0x8231606C,\n    0x823160F0,\n    0x82316210,\n]\n\n[[switch]]\nbase = 0x82317DA4\nr = 10\ndefault = 0x82317DE8\nlabels = [\n    0x82317DCC,\n    0x82317DD4,\n    0x82317DDC,\n    0x82317DE4,\n]\n\n[[switch]]\nbase = 0x8231846C\nr = 11\ndefault = 0x8231853C\nlabels = [\n    0x82318494,\n    0x823184BC,\n    0x823184E4,\n    0x8231850C,\n]\n\n[[switch]]\nbase = 0x8232685C\nr = 11\ndefault = 0x823269F4\nlabels = [\n    0x82326884,\n    0x82326884,\n    0x82326898,\n    0x823269D0,\n]\n\n[[switch]]\nbase = 0x8232893C\nr = 11\ndefault = 0x82328BB8\nlabels = [\n    0x82328A98,\n    0x82328B78,\n    0x82328964,\n    0x82328A44,\n]\n\n[[switch]]\nbase = 0x82328C10\nr = 11\ndefault = 0x82328E0C\nlabels = [\n    0x82328D28,\n    0x82328D48,\n    0x82328C38,\n    0x82328C58,\n]\n\n[[switch]]\nbase = 0x82328CC8\nr = 10\ndefault = 0x82328E0C\nlabels = [\n    0x82328E0C,\n    0x82328E0C,\n    0x82328E0C,\n    0x82328CF0,\n]\n\n[[switch]]\nbase = 0x82328DB8\nr = 10\ndefault = 0x82328E0C\nlabels = [\n    0x82328E0C,\n    0x82328DE0,\n    0x82328E0C,\n    0x82328E0C,\n]\n\n[[switch]]\nbase = 0x82333F98\nr = 10\ndefault = 0x82334060\nlabels = [\n    0x82333FFC,\n    0x82334060,\n    0x82334028,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334060,\n    0x82334038,\n    0x82334050,\n]\n\n[[switch]]\nbase = 0x82336A80\nr = 11\ndefault = 0x82336C24\nlabels = [\n    0x82336B64,\n    0x82336AE8,\n    0x82336B64,\n    0x82336C24,\n    0x82336AAC,\n]\n\n[[switch]]\nbase = 0x82336C98\nr = 11\ndefault = 0x82337338\nlabels = [\n    0x82336CD0,\n    0x82336D68,\n    0x82336D50,\n    0x8233715C,\n    0x823370F0,\n    0x82337264,\n    0x82337304,\n    0x82336FD0,\n]\n\n[[switch]]\nbase = 0x8233BA38\nr = 11\ndefault = 0x8233BBD4\nlabels = [\n    0x8233BA60,\n    0x8233BABC,\n    0x8233BAE4,\n    0x8233BB0C,\n]\n\n[[switch]]\nbase = 0x8233C0B0\nr = 11\ndefault = 0x8233C6D8\nlabels = [\n    0x8233C0D8,\n    0x8233C0EC,\n    0x8233C134,\n    0x8233C35C,\n]\n\n[[switch]]\nbase = 0x8234DE14\nr = 11\ndefault = 0x8234DFF0\nlabels = [\n    0x8234DE68,\n    0x8234DF00,\n    0x8234DF70,\n    0x8234DFF0,\n    0x8234DFF0,\n    0x8234DFF0,\n    0x8234DF90,\n    0x8234DF78,\n    0x8234DEB4,\n    0x8234DF9C,\n    0x8234DF1C,\n    0x8234DF38,\n    0x8234DF54,\n    0x8234DFC8,\n    0x8234DFE4,\n]\n\n[[switch]]\nbase = 0x82350448\nr = 10\ndefault = 0x82350BB0\nlabels = [\n    0x82350470,\n    0x823507BC,\n    0x82350960,\n    0x82350A88,\n]\n\n[[switch]]\nbase = 0x82351450\nr = 11\ndefault = 0x82351664\nlabels = [\n    0x82351494,\n    0x823514B4,\n    0x823514D4,\n    0x82351664,\n    0x82351664,\n    0x823516A4,\n    0x82351664,\n    0x82351664,\n    0x82351664,\n    0x82351664,\n    0x823514F4,\n]\n\n[[switch]]\nbase = 0x8235E42C\nr = 11\ndefault = 0x8235E680\nlabels = [\n    0x8235E458,\n    0x8235E4D8,\n    0x8235E5A4,\n    0x8235E5B0,\n    0x8235E5C8,\n]\n\n[[switch]]\nbase = 0x823622FC\nr = 11\ndefault = 0x82362444\nlabels = [\n    0x82362324,\n    0x82362444,\n    0x82362444,\n    0x82362394,\n]\n\n[[switch]]\nbase = 0x823623B0\nr = 11\ndefault = 0x82362444\nlabels = [\n    0x82362444,\n    0x823623D8,\n    0x823623E4,\n    0x82362444,\n]\n\n[[switch]]\nbase = 0x82362584\nr = 11\ndefault = 0x823625C8\nlabels = [\n    0x823625C8,\n    0x823625AC,\n    0x823625C8,\n    0x823625C8,\n]\n\n[[switch]]\nbase = 0x82371AEC\nr = 11\ndefault = 0x82371B64\nlabels = [\n    0x82371B18,\n    0x82371B28,\n    0x82371B38,\n    0x82371B48,\n    0x82371B58,\n]\n\n[[switch]]\nbase = 0x823742B8\nr = 28\ndefault = 0x82374390\nlabels = [\n    0x82374390,\n    0x823742E0,\n    0x823742E8,\n    0x823742F0,\n]\n\n[[switch]]\nbase = 0x8237530C\nr = 11\ndefault = 0x823753DC\nlabels = [\n    0x823753DC,\n    0x82375334,\n    0x8237533C,\n    0x82375344,\n]\n\n[[switch]]\nbase = 0x82379A58\nr = 11\ndefault = 0x82379C80\nlabels = [\n    0x82379A80,\n    0x82379AD4,\n    0x82379B9C,\n    0x82379BDC,\n]\n\n[[switch]]\nbase = 0x82382D2C\nr = 11\ndefault = 0x82383080\nlabels = [\n    0x82382D54,\n    0x82382DE8,\n    0x82382E68,\n    0x82382FDC,\n]\n\n[[switch]]\nbase = 0x8238361C\nr = 11\ndefault = 0x823836D4\nlabels = [\n    0x82383AB8,\n    0x82383648,\n    0x823836F0,\n    0x82383AD0,\n    0x82383B40,\n]\n\n[[switch]]\nbase = 0x823870D8\nr = 11\ndefault = 0x82387864\nlabels = [\n    0x82387100,\n    0x82387268,\n    0x82387358,\n    0x82387780,\n]\n\n[[switch]]\nbase = 0x8238969C\nr = 11\ndefault = 0x8238A08C\nlabels = [\n    0x82389FE4,\n    0x823896C4,\n    0x8238985C,\n    0x82389EA8,\n]\n\n[[switch]]\nbase = 0x8238C9D0\nr = 11\ndefault = 0x8238CAFC\nlabels = [\n    0x8238C9F8,\n    0x8238CA1C,\n    0x8238CA68,\n    0x8238CAB0,\n]\n\n[[switch]]\nbase = 0x8238CB60\nr = 31\ndefault = 0x8238D324\nlabels = [\n    0x8238CBA0,\n    0x8238CD08,\n    0x8238CE70,\n    0x8238CFA8,\n    0x8238CFD8,\n    0x8238CFD8,\n    0x8238CFE8,\n    0x8238D120,\n    0x8238D258,\n    0x8238D2A4,\n]\n\n[[switch]]\nbase = 0x8238D608\nr = 11\ndefault = 0x8238D778\nlabels = [\n    0x8238D640,\n    0x8238D640,\n    0x8238D6B8,\n    0x8238D6E4,\n    0x8238D6FC,\n    0x8238D73C,\n    0x8238D6B8,\n    0x8238D6B8,\n]\n\n[[switch]]\nbase = 0x8238EAB0\nr = 11\ndefault = 0x8238EB44\nlabels = [\n    0x8238EAF4,\n    0x8238EB08,\n    0x8238EB44,\n    0x8238EB44,\n    0x8238EB44,\n    0x8238EB30,\n    0x8238EB44,\n    0x8238EB44,\n    0x8238EB44,\n    0x8238EB44,\n    0x8238EB1C,\n]\n\n[[switch]]\nbase = 0x82394EC8\nr = 11\ndefault = 0x82394F64\nlabels = [\n    0x82394EF8,\n    0x82394F5C,\n    0x82394F64,\n    0x82394F00,\n    0x82394F08,\n    0x82394F5C,\n]\n\n[[switch]]\nbase = 0x82394FB4\nr = 11\ndefault = 0x82395310\nlabels = [\n    0x82394FE0,\n    0x8239510C,\n    0x82395310,\n    0x8239522C,\n    0x823952E8,\n]\n\n[[switch]]\nbase = 0x8239DC84\nr = 11\ndefault = 0x8239DF50\nlabels = [\n    0x8239DDA8,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DDB8,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DDC8,\n    0x8239DF50,\n    0x8239DDD8,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DF50,\n    0x8239DDE8,\n]\n\n[[switch]]\nbase = 0x823A1708\nr = 11\ndefault = 0x823A1928\nlabels = [\n    0x823A1738,\n    0x823A178C,\n    0x823A17D8,\n    0x823A1824,\n    0x823A1880,\n    0x823A18DC,\n]\n\n[[switch]]\nbase = 0x823A3268\nr = 11\ndefault = 0x823A32CC\nlabels = [\n    0x823A3290,\n    0x823A3330,\n    0x823A32A0,\n    0x823A32A0,\n]\n\n[[switch]]\nbase = 0x823A3368\nr = 11\ndefault = 0x823A33CC\nlabels = [\n    0x823A3390,\n    0x823A33A0,\n    0x823A3434,\n    0x823A3434,\n]\n\n[[switch]]\nbase = 0x823A3480\nr = 11\ndefault = 0x823A34E4\nlabels = [\n    0x823A34A8,\n    0x823A34B8,\n    0x823A354C,\n    0x823A354C,\n]\n\n[[switch]]\nbase = 0x823A4C04\nr = 11\ndefault = 0x823A4CCC\nlabels = [\n    0x823A4C34,\n    0x823A4D04,\n    0x823A4DB0,\n    0x823A4E18,\n    0x823A4F00,\n    0x823A4F88,\n]\n\n[[switch]]\nbase = 0x823A5BA0\nr = 11\ndefault = 0x823A604C\nlabels = [\n    0x823A5BD4,\n    0x823A5C50,\n    0x823A5D1C,\n    0x823A5DD8,\n    0x823A5E94,\n    0x823A5FD4,\n    0x823A6044,\n]\n\n[[switch]]\nbase = 0x823A9338\nr = 11\ndefault = 0x823A98FC\nlabels = [\n    0x823A9364,\n    0x823A93A8,\n    0x823A945C,\n    0x823A952C,\n    0x823A95F8,\n]\n\n[[switch]]\nbase = 0x823A98A0\nr = 11\ndefault = 0x823A98FC\nlabels = [\n    0x823A98D8,\n    0x823A98FC,\n    0x823A98D8,\n    0x823A98D8,\n    0x823A98D8,\n    0x823A98D8,\n    0x823A98FC,\n    0x823A98D8,\n]\n\n[[switch]]\nbase = 0x823AA500\nr = 11\ndefault = 0x823AA82C\nlabels = [\n    0x823AA52C,\n    0x823AA6E8,\n    0x823AA7A0,\n    0x823AA7B4,\n    0x823AA7E4,\n]\n\n[[switch]]\nbase = 0x823AAB64\nr = 11\ndefault = 0x823AAE7C\nlabels = [\n    0x823AAB90,\n    0x823AAC70,\n    0x823AAD68,\n    0x823AAE28,\n    0x823AAE50,\n]\n\n[[switch]]\nbase = 0x823AB9A8\nr = 11\ndefault = 0x823ABB68\nlabels = [\n    0x823AB9D0,\n    0x823ABA7C,\n    0x823ABAD4,\n    0x823ABB3C,\n]\n\n[[switch]]\nbase = 0x823AC1CC\nr = 11\ndefault = 0x823AC4B8\nlabels = [\n    0x823AC1F8,\n    0x823AC4B8,\n    0x823AC290,\n    0x823AC43C,\n    0x823AC48C,\n]\n\n[[switch]]\nbase = 0x823ACFDC\nr = 11\ndefault = 0x823AD2D8\nlabels = [\n    0x823AD004,\n    0x823AD09C,\n    0x823AD280,\n    0x823AD2D0,\n]\n\n[[switch]]\nbase = 0x823ADE58\nr = 11\ndefault = 0x823AE100\nlabels = [\n    0x823ADE80,\n    0x823ADEDC,\n    0x823ADF1C,\n    0x823AE0BC,\n]\n\n[[switch]]\nbase = 0x823AFFFC\nr = 11\ndefault = 0x823B05C4\nlabels = [\n    0x823B0030,\n    0x823B02D8,\n    0x823B0080,\n    0x823B0194,\n    0x823B0370,\n    0x823B0530,\n    0x823B05BC,\n]\n\n[[switch]]\nbase = 0x823B0C78\nr = 11\ndefault = 0x823B1214\nlabels = [\n    0x823B0CA8,\n    0x823B0CAC,\n    0x823B0DA4,\n    0x823B1074,\n    0x823B0E40,\n    0x823B0EB8,\n]\n\n[[switch]]\nbase = 0x823B1634\nr = 11\ndefault = 0x823B1BC8\nlabels = [\n    0x823B1668,\n    0x823B1734,\n    0x823B1848,\n    0x823B1990,\n    0x823B1A4C,\n    0x823B1938,\n    0x823B17E4,\n]\n\n[[switch]]\nbase = 0x823B3350\nr = 11\ndefault = 0x823B3398\nlabels = [\n    0x823B3378,\n    0x823B33D8,\n    0x823B3418,\n    0x823B3438,\n]\n\n[[switch]]\nbase = 0x823B4AB0\nr = 11\ndefault = 0x823B4CD0\nlabels = [\n    0x823B4AD8,\n    0x823B4AF4,\n    0x823B4BD0,\n    0x823B4CC8,\n]\n\n[[switch]]\nbase = 0x823B4D9C\nr = 11\ndefault = 0x823B4E58\nlabels = [\n    0x823B4DC4,\n    0x823B4DE4,\n    0x823B4E1C,\n    0x823B4E3C,\n]\n\n[[switch]]\nbase = 0x823B6044\nr = 5\ndefault = 0x823B6194\nlabels = [\n    0x823B6070,\n    0x823B6128,\n    0x823B6134,\n    0x823B6140,\n    0x823B614C,\n]\n\n[[switch]]\nbase = 0x823B6B88\nr = 11\ndefault = 0x823B6DD8\nlabels = [\n    0x823B6BB0,\n    0x823B6BEC,\n    0x823B6C18,\n    0x823B6D54,\n]\n\n[[switch]]\nbase = 0x823C15D8\nr = 11\ndefault = 0x823C1814\nlabels = [\n    0x823C1608,\n    0x823C1668,\n    0x823C16C8,\n    0x823C171C,\n    0x823C17D4,\n    0x823C1814,\n]\n\n[[switch]]\nbase = 0x823C316C\nr = 11\ndefault = 0x823C31A4\nlabels = [\n    0x823C3194,\n    0x823C31B4,\n    0x823C31D4,\n    0x823C31F4,\n]\n\n[[switch]]\nbase = 0x823C3D60\nr = 11\ndefault = 0x823C3FF4\nlabels = [\n    0x823C3D90,\n    0x823C3DD8,\n    0x823C3E30,\n    0x823C3F90,\n    0x823C3FA4,\n    0x823C3FD4,\n]\n\n[[switch]]\nbase = 0x823C482C\nr = 11\ndefault = 0x823C498C\nlabels = [\n    0x823C4854,\n    0x823C4890,\n    0x823C48B0,\n    0x823C4928,\n]\n\n[[switch]]\nbase = 0x823C4E6C\nr = 11\ndefault = 0x823C55F4\nlabels = [\n    0x823C4EA0,\n    0x823C4F50,\n    0x823C4F64,\n    0x823C5094,\n    0x823C52E0,\n    0x823C5558,\n    0x823C55D4,\n]\n\n[[switch]]\nbase = 0x823C772C\nr = 11\ndefault = 0x823C7F2C\nlabels = [\n    0x823C7764,\n    0x823C77DC,\n    0x823C795C,\n    0x823C7BE4,\n    0x823C7C80,\n    0x823C7D94,\n    0x823C7DE8,\n    0x823C7EA8,\n]\n\n[[switch]]\nbase = 0x823C8CC0\nr = 11\ndefault = 0x823C8E34\nlabels = [\n    0x823C8CE8,\n    0x823C8D30,\n    0x823C8E2C,\n    0x823C8DA4,\n]\n\n[[switch]]\nbase = 0x823CAD08\nr = 11\ndefault = 0x823CAE04\nlabels = [\n    0x823CAD30,\n    0x823CADA0,\n    0x823CADBC,\n    0x823CADEC,\n]\n\n[[switch]]\nbase = 0x823D2FF8\nr = 11\ndefault = 0x823D3234\nlabels = [\n    0x823D3024,\n    0x823D3204,\n    0x823D3210,\n    0x823D321C,\n    0x823D3228,\n]\n\n[[switch]]\nbase = 0x823D33D4\nr = 11\ndefault = 0x823D3234\nlabels = [\n    0x823D3408,\n    0x823D3400,\n    0x823D3408,\n    0x823D3408,\n    0x823D3408,\n]\n\n[[switch]]\nbase = 0x823D4774\nr = 11\ndefault = 0x823D499C\nlabels = [\n    0x823D47A4,\n    0x823D47F8,\n    0x823D484C,\n    0x823D48A0,\n    0x823D4948,\n    0x823D48F4,\n]\n\n[[switch]]\nbase = 0x823D49B4\nr = 11\ndefault = 0x823D4B5C\nlabels = [\n    0x823D49E4,\n    0x823D4A28,\n    0x823D4A6C,\n    0x823D4AB0,\n    0x823D4B30,\n    0x823D4AF4,\n]\n\n[[switch]]\nbase = 0x823DC7A8\nr = 11\ndefault = 0x823DC994\nlabels = [\n    0x823DC7D4,\n    0x823DC994,\n    0x823DC7EC,\n    0x823DC81C,\n    0x823DC804,\n]\n\n[[switch]]\nbase = 0x823DC85C\nr = 11\ndefault = 0x823DC994\nlabels = [\n    0x823DC8D4,\n    0x823DC8EC,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC91C,\n    0x823DC994,\n    0x823DC994,\n    0x823DC744,\n    0x823DC994,\n    0x823DC904,\n    0x823DC97C,\n    0x823DC934,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC994,\n    0x823DC94C,\n    0x823DC964,\n]\n\n[[switch]]\nbase = 0x823DE214\nr = 11\ndefault = 0x823DE45C\nlabels = [\n    0x823DE23C,\n    0x823DE258,\n    0x823DE274,\n    0x823DE294,\n]\n\n[[switch]]\nbase = 0x823DE2DC\nr = 11\ndefault = 0x823DE45C\nlabels = [\n    0x823DE354,\n    0x823DE45C,\n    0x823DE370,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE424,\n    0x823DE45C,\n    0x823DE440,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE45C,\n    0x823DE38C,\n    0x823DE3A8,\n    0x823DE3C4,\n    0x823DE3E4,\n    0x823DE400,\n]\n\n[[switch]]\nbase = 0x823E02E0\nr = 11\ndefault = 0x823E0568\nlabels = [\n    0x823E030C,\n    0x823E0568,\n    0x823E032C,\n    0x823E036C,\n    0x823E034C,\n]\n\n[[switch]]\nbase = 0x823E03BC\nr = 11\ndefault = 0x823E0568\nlabels = [\n    0x823E04A8,\n    0x823E046C,\n    0x823E0568,\n    0x823E0568,\n    0x823E04C8,\n    0x823E0568,\n    0x823E0298,\n    0x823E0298,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E04E8,\n    0x823E0548,\n    0x823E0528,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0568,\n    0x823E0508,\n]\n\n[[switch]]\nbase = 0x823E1484\nr = 11\ndefault = 0x823E15E4\nlabels = [\n    0x823E1528,\n    0x823E15E4,\n    0x823E14E8,\n    0x823E1508,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15A4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15C4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E15E4,\n    0x823E1564,\n    0x823E1584,\n]\n\n[[switch]]\nbase = 0x823E2ED0\nr = 11\ndefault = 0x823E30B4\nlabels = [\n    0x823E2F04,\n    0x823E30B4,\n    0x823E2F1C,\n    0x823E30B4,\n    0x823E2F34,\n    0x823E30B4,\n    0x823E2F4C,\n]\n\n[[switch]]\nbase = 0x823E2F8C\nr = 11\ndefault = 0x823E30B4\nlabels = [\n    0x823E2FDC,\n    0x823E30B4,\n    0x823E2FF4,\n    0x823E30B4,\n    0x823E303C,\n    0x823E306C,\n    0x823E3054,\n    0x823E30B4,\n    0x823E30B4,\n    0x823E300C,\n    0x823E3024,\n    0x823E30B4,\n    0x823E3084,\n    0x823E309C,\n]\n\n[[switch]]\nbase = 0x823E5820\nr = 11\ndefault = 0x823E5A98\nlabels = [\n    0x823E5858,\n    0x823E5A98,\n    0x823E5870,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5888,\n    0x823E58A0,\n]\n\n[[switch]]\nbase = 0x823E58E0\nr = 11\ndefault = 0x823E5A98\nlabels = [\n    0x823E59D8,\n    0x823E5A98,\n    0x823E59F0,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E57A4,\n    0x823E5A08,\n    0x823E5A20,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A38,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A98,\n    0x823E5A50,\n    0x823E5A68,\n    0x823E5A80,\n]\n\n[[switch]]\nbase = 0x823E7CE8\nr = 28\ndefault = 0x823E7F44\nlabels = [\n    0x823E7D18,\n    0x823E7D60,\n    0x823E7DA8,\n    0x823E7E70,\n    0x823E7EF8,\n    0x823E7F40,\n]\n\n[[switch]]\nbase = 0x823EB570\nr = 11\ndefault = 0x823EB6AC\nlabels = [\n    0x823EB4D4,\n    0x823EB4D4,\n    0x823EB4D4,\n    0x823EB4D4,\n]\n\n[[switch]]\nbase = 0x823EB5A8\nr = 11\ndefault = 0x823EB6AC\nlabels = [\n    0x823EB4D4,\n    0x823EB6AC,\n    0x823EB4D4,\n    0x823EB4D4,\n    0x823EB4D4,\n    0x823EB6AC,\n    0x823EB4D4,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB4D4,\n    0x823EB668,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB6AC,\n    0x823EB680,\n    0x823EB650,\n]\n\n[[switch]]\nbase = 0x823ECD7C\nr = 11\ndefault = 0x823ECDFC\nlabels = [\n    0x823ECDA4,\n    0x823ECDA4,\n    0x823ECDDC,\n    0x823ECDC0,\n]\n\n[[switch]]\nbase = 0x823EFEBC\nr = 11\ndefault = 0x823EFF98\nlabels = [\n    0x823EFF04,\n    0x823EFF24,\n    0x823EFF98,\n    0x823EFF40,\n    0x823EFF98,\n    0x823EFF98,\n    0x823EFF98,\n    0x823EFF98,\n    0x823EFF7C,\n    0x823EFF98,\n    0x823EFDFC,\n    0x823EFF54,\n]\n\n[[switch]]\nbase = 0x823F32AC\nr = 11\ndefault = 0x823F3478\nlabels = [\n    0x823F32E8,\n    0x823F3478,\n    0x823F3478,\n    0x823F3478,\n    0x823F3478,\n    0x823F3478,\n    0x823F32FC,\n    0x823F3314,\n    0x823F3324,\n]\n\n[[switch]]\nbase = 0x823F335C\nr = 11\ndefault = 0x823F3478\nlabels = [\n    0x823F3414,\n    0x823F3428,\n    0x823F3478,\n    0x823F3478,\n    0x823F3478,\n    0x823F33B8,\n    0x823F33CC,\n    0x823F33E0,\n    0x823F3478,\n    0x823F3478,\n    0x823F3478,\n    0x823F3400,\n    0x823F3450,\n    0x823F3478,\n    0x823F343C,\n    0x823F3478,\n    0x823F3464,\n]\n\n[[switch]]\nbase = 0x823F4484\nr = 11\ndefault = 0x823F4578\nlabels = [\n    0x823F44D0,\n    0x823F44E8,\n    0x823F4500,\n    0x823F4578,\n    0x823F4518,\n    0x823F4578,\n    0x823F4548,\n    0x823F4560,\n    0x823F4578,\n    0x823F4578,\n    0x823F4578,\n    0x823F4578,\n    0x823F4530,\n]\n\n[[switch]]\nbase = 0x823F6C40\nr = 11\ndefault = 0x823F6DCC\nlabels = [\n    0x823F6D3C,\n    0x823F6D54,\n    0x823F6DCC,\n    0x823F6D6C,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6D84,\n    0x823F6D9C,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DCC,\n    0x823F6DB4,\n]\n\n[[switch]]\nbase = 0x823F7D74\nr = 11\ndefault = 0x823F7DD0\nlabels = [\n    0x823F7D9C,\n    0x823F7DA8,\n    0x823F7DB4,\n    0x823F7DC0,\n]\n\n[[switch]]\nbase = 0x823F8C38\nr = 11\ndefault = 0x823F8D00\nlabels = [\n    0x823F8BAC,\n    0x823F8D00,\n    0x823F8C88,\n    0x823F8D00,\n    0x823F8CB8,\n    0x823F8D00,\n    0x823F8D00,\n    0x823F8D00,\n    0x823F8D00,\n    0x823F8D00,\n    0x823F8CA0,\n    0x823F8D00,\n    0x823F8CD0,\n    0x823F8CE8,\n]\n\n[[switch]]\nbase = 0x823F9FC0\nr = 11\ndefault = 0x823FA0F8\nlabels = [\n    0x823FA010,\n    0x823FA0F8,\n    0x823FA038,\n    0x823FA0F8,\n    0x823FA098,\n    0x823FA0F8,\n    0x823FA0F8,\n    0x823FA0F8,\n    0x823FA058,\n    0x823F9E68,\n    0x823FA078,\n    0x823FA0F8,\n    0x823FA0B8,\n    0x823FA0D8,\n]\n\n[[switch]]\nbase = 0x823FB48C\nr = 11\ndefault = 0x823FB680\nlabels = [\n    0x823FB60C,\n    0x823FB680,\n    0x823FB620,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB3B4,\n    0x823FB634,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB680,\n    0x823FB644,\n    0x823FB66C,\n]\n\n[[switch]]\nbase = 0x823FE0EC\nr = 11\ndefault = 0x823FE42C\nlabels = [\n    0x823FE18C,\n    0x823FE42C,\n    0x823FE178,\n    0x823FE42C,\n    0x823FE1A0,\n    0x823FE1B4,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE1C8,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE1F0,\n]\n\n[[switch]]\nbase = 0x823FE228\nr = 11\ndefault = 0x823FE42C\nlabels = [\n    0x823FE344,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE36C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE358,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE42C,\n    0x823FE404,\n    0x823FE418,\n    0x823FE3A0,\n    0x823FE42C,\n    0x823FE3B4,\n    0x823FE38C,\n    0x823FE3C8,\n    0x823FE3DC,\n    0x823FE3F0,\n    0x823FE308,\n    0x823FE318,\n]\n\n[[switch]]\nbase = 0x8240083C\nr = 11\ndefault = 0x82400940\nlabels = [\n    0x824008E0,\n    0x824008F4,\n    0x82400908,\n    0x8240092C,\n    0x824008CC,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x82400940,\n    0x824008B4,\n]\n\n[[switch]]\nbase = 0x8240284C\nr = 11\ndefault = 0x82402970\nlabels = [\n    0x8240291C,\n    0x82402970,\n    0x82402970,\n    0x824028F4,\n    0x82402908,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402970,\n    0x82402948,\n    0x8240295C,\n]\n\n[[switch]]\nbase = 0x8240BFFC\nr = 11\ndefault = 0x8240C02C\nlabels = [\n    0x8240C068,\n    0x8240C068,\n    0x8240C02C,\n    0x8240C02C,\n    0x8240C068,\n    0x8240C068,\n]\n\n[[switch]]\nbase = 0x8240CBB8\nr = 11\ndefault = 0x8240CCA8\nlabels = [\n    0x8240CCC4,\n    0x8240CBE8,\n    0x8240CCA8,\n    0x8240CCA8,\n    0x8240CC28,\n    0x8240CC68,\n]\n\n[[switch]]\nbase = 0x8240F69C\nr = 11\ndefault = 0x8240F8AC\nlabels = [\n    0x8240F888,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F830,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F844,\n    0x8240F858,\n    0x8240F874,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F8AC,\n    0x8240F89C,\n]\n\n[[switch]]\nbase = 0x82410C5C\nr = 11\ndefault = 0x82410CB8\nlabels = [\n    0x82410C9C,\n    0x82410C9C,\n    0x82410C90,\n    0x82410C84,\n]\n\n[[switch]]\nbase = 0x82412FF0\nr = 11\ndefault = 0x824130B4\nlabels = [\n    0x82413018,\n    0x82413044,\n    0x82413068,\n    0x82413094,\n]\n\n[[switch]]\nbase = 0x824131A8\nr = 11\ndefault = 0x824132B8\nlabels = [\n    0x8241312C,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x82413288,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x824132B8,\n    0x82413298,\n    0x824132A8,\n]\n\n[[switch]]\nbase = 0x82416778\nr = 11\ndefault = 0x82416834\nlabels = [\n    0x824167AC,\n    0x824167BC,\n    0x824167D0,\n    0x824167E4,\n    0x824167F8,\n    0x82416820,\n    0x8241680C,\n]\n\n[[switch]]\nbase = 0x82422BF4\nr = 11\ndefault = 0x82422D58\nlabels = [\n    0x82422D38,\n    0x82422CD8,\n    0x82422CF8,\n    0x82422D18,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422D58,\n    0x82422CB4,\n]\n\n[[switch]]\nbase = 0x824244A8\nr = 11\ndefault = 0x824245E0\nlabels = [\n    0x8242456C,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x82424554,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x824245E0,\n    0x82424584,\n    0x8242459C,\n    0x824245B4,\n]\n\n[[switch]]\nbase = 0x824256F4\nr = 11\ndefault = 0x82425934\nlabels = [\n    0x8242586C,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x824256AC,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x82425934,\n    0x824258E0,\n    0x8242588C,\n]\n\n[[switch]]\nbase = 0x82426B70\nr = 11\ndefault = 0x82426C20\nlabels = [\n    0x82426B98,\n    0x82426BB0,\n    0x82426BC8,\n    0x82426BE0,\n]\n\n[[switch]]\nbase = 0x82428D48\nr = 11\ndefault = 0x82428DB8\nlabels = [\n    0x82428D70,\n    0x82428D84,\n    0x82428D98,\n    0x82428DAC,\n]\n\n[[switch]]\nbase = 0x82434738\nr = 11\ndefault = 0x8243488C\nlabels = [\n    0x824347AC,\n    0x824347C8,\n    0x824347E4,\n    0x82434800,\n    0x8243488C,\n    0x8243481C,\n    0x8243488C,\n    0x82434838,\n    0x8243488C,\n    0x82434854,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x8243488C,\n    0x82434870,\n]\n\n[[switch]]\nbase = 0x82439CC8\nr = 11\ndefault = 0x8243A538\nlabels = [\n    0x82439D00,\n    0x8243A108,\n    0x8243A530,\n    0x8243A518,\n    0x8243A538,\n    0x82439FCC,\n    0x82439FF0,\n    0x8243A530,\n]\n\n[[switch]]\nbase = 0x82442F38\nr = 5\ndefault = 0x824430F0\nlabels = [\n    0x82442F60,\n    0x82442FB8,\n    0x82443014,\n    0x8244306C,\n]\n\n[[switch]]\nbase = 0x82449CF0\nr = 11\ndefault = 0x82449D60\nlabels = [\n    0x82449D4C,\n    0x82449D4C,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D60,\n    0x82449D4C,\n    0x82449D4C,\n]\n\n[[switch]]\nbase = 0x8244AC4C\nr = 11\ndefault = 0x8244ACA0\nlabels = [\n    0x8244AC98,\n    0x8244ACA0,\n    0x8244ACA0,\n    0x8244ACA0,\n    0x8244ACA0,\n    0x8244ACA0,\n    0x8244AC98,\n    0x8244ACA0,\n    0x8244ACA0,\n    0x8244AC98,\n    0x8244ACA0,\n    0x8244ACA0,\n    0x8244AC98,\n]\n\n[[switch]]\nbase = 0x8244B938\nr = 4\ndefault = 0x0\nlabels = [\n    0x8244B960,\n    0x8244B964,\n    0x8244B968,\n    0x8244B96C,\n]\n\n[[switch]]\nbase = 0x8245AF2C\nr = 11\ndefault = 0x8245AFD8\nlabels = [\n    0x8245AF60,\n    0x8245AF60,\n    0x8245AF60,\n    0x8245AF94,\n    0x8245AF60,\n    0x8245AF94,\n    0x8245AF60,\n]\n\n[[switch]]\nbase = 0x824A02D8\nr = 11\ndefault = 0x824A03B4\nlabels = [\n    0x824A0308,\n    0x824A03B4,\n    0x824A0320,\n    0x824A03B4,\n    0x824A0354,\n    0x824A0388,\n]\n\n[[switch]]\nbase = 0x824A08A0\nr = 11\ndefault = 0x824A0998\nlabels = [\n    0x824A08D4,\n    0x824A08E0,\n    0x824A08EC,\n    0x824A090C,\n    0x824A0934,\n    0x824A0974,\n    0x824A0994,\n]\n\n[[switch]]\nbase = 0x824A6194\nr = 10\ndefault = 0x824A6200\nlabels = [\n    0x824A61D0,\n    0x824A61E0,\n    0x824A6200,\n    0x824A6200,\n    0x824A61F0,\n    0x824A6200,\n    0x824A6200,\n    0x824A6200,\n    0x824A61E0,\n]\n\n[[switch]]\nbase = 0x824A6434\nr = 11\ndefault = 0x824A6660\nlabels = [\n    0x824A6648,\n    0x824A6650,\n    0x824A6660,\n    0x824A647C,\n    0x824A6508,\n    0x824A6640,\n    0x824A6660,\n    0x824A6658,\n    0x824A6660,\n    0x824A6640,\n    0x824A6660,\n    0x824A65F8,\n]\n\n[[switch]]\nbase = 0x824A6FB0\nr = 11\ndefault = 0x824A758C\nlabels = [\n    0x824A6FF8,\n    0x824A7074,\n    0x824A70F0,\n    0x824A716C,\n    0x824A71CC,\n    0x824A729C,\n    0x824A72EC,\n    0x824A7334,\n    0x824A7468,\n    0x824A7560,\n    0x824A74F8,\n    0x824A7234,\n]\n\n[[switch]]\nbase = 0x824A9F6C\nr = 11\ndefault = 0x824AA090\nlabels = [\n    0x824A9FA0,\n    0x824AA090,\n    0x824A9FA0,\n    0x824AA090,\n    0x824A9FE8,\n    0x824AA090,\n    0x824AA03C,\n]\n\n[[switch]]\nbase = 0x824ABAB0\nr = 10\ndefault = 0x824ABAF0\nlabels = [\n    0x824ABAD8,\n    0x824ABAE0,\n    0x824ABAE0,\n    0x824ABAE8,\n]\n\n[[switch]]\nbase = 0x824AEBA4\nr = 10\ndefault = 0x824AEC14\nlabels = [\n    0x824AEBD0,\n    0x824AEBE0,\n    0x824AEBF0,\n    0x824AEBF8,\n    0x824AEC08,\n]\n\n[[switch]]\nbase = 0x824B1C48\nr = 11\ndefault = 0x824B1EF0\nlabels = [\n    0x824B1D54,\n    0x824B1D78,\n    0x824B1D84,\n    0x824B1D90,\n    0x824B1E0C,\n    0x824B1CEC,\n    0x824B1D1C,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1DA0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1E30,\n    0x824B1E4C,\n    0x824B1E60,\n    0x824B1E74,\n    0x824B1E88,\n    0x824B1E9C,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EB0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1EF0,\n    0x824B1DE0,\n]\n\n[[switch]]\nbase = 0x824B2AEC\nr = 9\ndefault = 0x824B3080\nlabels = [\n    0x824B2B90,\n    0x824B3080,\n    0x824B2B58,\n    0x824B2B24,\n    0x824B2B74,\n    0x824B3080,\n    0x824B2AB4,\n    0x824B2AAC,\n]\n\n[[switch]]\nbase = 0x824B2E44\nr = 9\ndefault = 0x824B3080\nlabels = [\n    0x824B2E8C,\n    0x824B2EA8,\n    0x824B2ECC,\n    0x824B2EE8,\n    0x824B2F6C,\n    0x824B2F88,\n    0x824B303C,\n    0x824B2F14,\n    0x824B2F30,\n    0x824B2FF4,\n    0x824B2FAC,\n    0x824B2AB4,\n]\n\n[[switch]]\nbase = 0x824B6328\nr = 11\ndefault = 0x824B67EC\nlabels = [\n    0x824B636C,\n    0x824B63D4,\n    0x824B6438,\n    0x824B6494,\n    0x824B6504,\n    0x824B6568,\n    0x824B65CC,\n    0x824B6630,\n    0x824B6694,\n    0x824B66F0,\n    0x824B674C,\n]\n\n[[switch]]\nbase = 0x824BA110\nr = 11\ndefault = 0x824BA1C4\nlabels = [\n    0x824BA1C4,\n    0x824BA138,\n    0x824BA174,\n    0x824BA1A0,\n]\n\n[[switch]]\nbase = 0x824C281C\nr = 29\ndefault = 0x824C2860\nlabels = [\n    0x824C2844,\n    0x824C284C,\n    0x824C2858,\n    0x824C2858,\n]\n\n[[switch]]\nbase = 0x824C44A8\nr = 3\ndefault = 0x824C4540\nlabels = [\n    0x824C44D0,\n    0x824C44EC,\n    0x824C4508,\n    0x824C4524,\n]\n\n[[switch]]\nbase = 0x824C4558\nr = 3\ndefault = 0x824C45B0\nlabels = [\n    0x824C4580,\n    0x824C458C,\n    0x824C4598,\n    0x824C45A4,\n]\n\n[[switch]]\nbase = 0x824C45C0\nr = 3\ndefault = 0x824C4600\nlabels = [\n    0x824C45E8,\n    0x824C45F0,\n    0x824C45F0,\n    0x824C45F8,\n]\n\n[[switch]]\nbase = 0x824C480C\nr = 4\ndefault = 0x824C4864\nlabels = [\n    0x824C4834,\n    0x824C4840,\n    0x824C484C,\n    0x824C4858,\n]\n\n[[switch]]\nbase = 0x824C4878\nr = 4\ndefault = 0x824C48B8\nlabels = [\n    0x824C48A0,\n    0x824C48A8,\n    0x824C48A8,\n    0x824C48B0,\n]\n\n[[switch]]\nbase = 0x824C494C\nr = 4\ndefault = 0x824C4A44\nlabels = [\n    0x824C4974,\n    0x824C49AC,\n    0x824C49E4,\n    0x824C4A1C,\n]\n\n[[switch]]\nbase = 0x824C4F00\nr = 31\ndefault = 0x824C4F84\nlabels = [\n    0x824C4F28,\n    0x824C4F74,\n    0x824C4F7C,\n    0x824C4F34,\n]\n\n[[switch]]\nbase = 0x824C6DC4\nr = 11\ndefault = 0x824C6FF4\nlabels = [\n    0x824C6DEC,\n    0x824C6E3C,\n    0x824C6EC0,\n    0x824C6F54,\n]\n\n[[switch]]\nbase = 0x824C8AA8\nr = 11\ndefault = 0x824C8FE8\nlabels = [\n    0x824C8AD0,\n    0x824C8B28,\n    0x824C8E24,\n    0x824C8FB4,\n]\n\n[[switch]]\nbase = 0x824C9680\nr = 11\ndefault = 0x824C985C\nlabels = [\n    0x824C96AC,\n    0x824C96F4,\n    0x824C973C,\n    0x824C976C,\n    0x824C97F4,\n]\n\n[[switch]]\nbase = 0x824CC374\nr = 11\ndefault = 0x824CC728\nlabels = [\n    0x824CC3B4,\n    0x824CC3F0,\n    0x824CC410,\n    0x824CC474,\n    0x824CC4BC,\n    0x824CC5AC,\n    0x824CC5CC,\n    0x824CC648,\n    0x824CC690,\n    0x824CC6F0,\n]\n\n[[switch]]\nbase = 0x824D1334\nr = 11\ndefault = 0x824D15F4\nlabels = [\n    0x824D1374,\n    0x824D15F4,\n    0x824D15F4,\n    0x824D15F4,\n    0x824D15F4,\n    0x824D15F4,\n    0x824D14B4,\n    0x824D15CC,\n    0x824D15F4,\n    0x824D1670,\n]\n\n[[switch]]\nbase = 0x824D2DC4\nr = 11\ndefault = 0x824D2FE0\nlabels = [\n    0x824D2E00,\n    0x824D2EA0,\n    0x824D2EC4,\n    0x824D2F50,\n    0x824D2F8C,\n    0x824D2FA4,\n    0x824D2FBC,\n    0x824D2E88,\n    0x824D2FD4,\n]\n\n[[switch]]\nbase = 0x824D3320\nr = 11\ndefault = 0x824D35E8\nlabels = [\n    0x824D3374,\n    0x824D33A0,\n    0x824D35E8,\n    0x824D33D4,\n    0x824D3408,\n    0x824D342C,\n    0x824D3460,\n    0x824D3484,\n    0x824D34B8,\n    0x824D34DC,\n    0x824D3510,\n    0x824D3534,\n    0x824D3568,\n    0x824D358C,\n    0x824D35C0,\n]\n\n[[switch]]\nbase = 0x824D3B4C\nr = 4\ndefault = 0x824D423C\nlabels = [\n    0x824D3B80,\n    0x824D3BC8,\n    0x824D3C10,\n    0x824D40F0,\n    0x824D4144,\n    0x824D4198,\n    0x824D41EC,\n]\n\n[[switch]]\nbase = 0x824D5580\nr = 3\ndefault = 0x824D5748\nlabels = [\n    0x824D55A8,\n    0x824D55D8,\n    0x824D5608,\n    0x824D5638,\n]\n\n[[switch]]\nbase = 0x824D5670\nr = 3\ndefault = 0x824D5748\nlabels = [\n    0x824D5698,\n    0x824D56C4,\n    0x824D56F0,\n    0x824D571C,\n]\n\n[[switch]]\nbase = 0x824D5798\nr = 3\ndefault = 0x824D5858\nlabels = [\n    0x824D57C0,\n    0x824D57E4,\n    0x824D5808,\n    0x824D582C,\n]\n\n[[switch]]\nbase = 0x824D68D8\nr = 11\ndefault = 0x824D6BD0\nlabels = [\n    0x824D6934,\n    0x824D6964,\n    0x824D6BD0,\n    0x824D69F4,\n    0x824D6A58,\n    0x824D6ABC,\n    0x824D6AE8,\n    0x824D6B0C,\n    0x824D6BD0,\n    0x824D6B30,\n    0x824D6B30,\n    0x824D6B84,\n    0x824D6B9C,\n    0x824D6BD0,\n    0x824D6B8C,\n    0x824D6BA8,\n    0x824D6BA8,\n]\n\n[[switch]]\nbase = 0x824DEE80\nr = 11\ndefault = 0x824DF020\nlabels = [\n    0x824DEEF8,\n    0x824DEF0C,\n    0x824DEF20,\n    0x824DEF34,\n    0x824DEF5C,\n    0x824DEF48,\n    0x824DEF70,\n    0x824DEF84,\n    0x824DEF98,\n    0x824DEFAC,\n    0x824DF020,\n    0x824DF020,\n    0x824DF020,\n    0x824DF020,\n    0x824DEFC0,\n    0x824DEFD4,\n    0x824DEFE8,\n    0x824DEFFC,\n    0x824DF034,\n    0x824DF058,\n    0x824DF06C,\n    0x824DF080,\n    0x824DF094,\n    0x824DF0A8,\n]\n\n[[switch]]\nbase = 0x824DF0E8\nr = 9\ndefault = 0x824DF520\nlabels = [\n    0x824DF128,\n    0x824DF148,\n    0x824DF198,\n    0x824DF218,\n    0x824DF298,\n    0x824DF2F4,\n    0x824DF360,\n    0x824DF3B4,\n    0x824DF440,\n    0x824DF48C,\n]\n\n[[switch]]\nbase = 0x824E825C\nr = 10\ndefault = 0x824E9844\nlabels = [\n    0x824E8684,\n    0x824E8734,\n    0x824E8880,\n    0x824E8928,\n    0x824E8928,\n    0x824E8950,\n    0x824E89B4,\n    0x824E9844,\n    0x824E8A64,\n    0x824E8B44,\n    0x824E89D0,\n    0x824E8B98,\n    0x824E8B98,\n    0x824E8BB4,\n    0x824E8C0C,\n    0x824E8C60,\n    0x824E8C60,\n    0x824E8C7C,\n    0x824E8CD4,\n    0x824E8D28,\n    0x824E8D28,\n    0x824E8D44,\n    0x824E8D9C,\n    0x824E8DF0,\n    0x824E8DF0,\n    0x824E8E84,\n    0x824E8E98,\n    0x824E8EF8,\n    0x824E8F14,\n    0x824E8F14,\n    0x824E8F30,\n    0x824E8FCC,\n    0x824E9024,\n    0x824E9024,\n    0x824E9044,\n    0x824E9058,\n    0x824E90AC,\n    0x824E9114,\n    0x824E9158,\n    0x824E9174,\n    0x824E9174,\n    0x824E9190,\n    0x824E9224,\n    0x824E9280,\n    0x824E9280,\n    0x824E92B8,\n    0x824E92CC,\n    0x824E9350,\n    0x824E9350,\n    0x824E92B8,\n    0x824E9380,\n    0x824E9444,\n    0x824E9444,\n    0x824E9458,\n    0x824E94A8,\n    0x824E94C8,\n    0x824E94C8,\n    0x824E9844,\n    0x824E94E4,\n    0x824E9538,\n    0x824E9538,\n    0x824E9538,\n    0x824E954C,\n    0x824E95A0,\n    0x824E95A0,\n    0x824E95A0,\n    0x824E9844,\n    0x824E95B4,\n    0x824E95B4,\n    0x824E95B4,\n    0x824E9844,\n    0x824E95C8,\n    0x824E95C8,\n    0x824E95C8,\n    0x824E9844,\n    0x824E95DC,\n    0x824E95DC,\n    0x824E95DC,\n    0x824E95F0,\n    0x824E9644,\n    0x824E9644,\n    0x824E9644,\n    0x824E9658,\n    0x824E9658,\n    0x824E9844,\n    0x824E9844,\n    0x824E967C,\n    0x824E96D0,\n    0x824E9724,\n    0x824E9778,\n    0x824E97CC,\n    0x824E9844,\n    0x824E845C,\n    0x824E84A4,\n    0x824E84EC,\n    0x824E8534,\n    0x824E857C,\n    0x824E85C4,\n    0x824E8748,\n    0x824E8760,\n    0x824E8778,\n    0x824E8790,\n    0x824E87A8,\n    0x824E87C0,\n    0x824E87D8,\n    0x824E87F0,\n    0x824E8808,\n    0x824E8820,\n    0x824E8838,\n    0x824E9844,\n    0x824E9844,\n    0x824E9844,\n    0x824E9368,\n    0x824E8868,\n    0x824E9844,\n    0x824E860C,\n    0x824E8624,\n    0x824E863C,\n    0x824E9844,\n    0x824E9844,\n    0x824E8654,\n    0x824E866C,\n]\n\n[[switch]]\nbase = 0x824E9FF8\nr = 10\ndefault = 0x824EA1E4\nlabels = [\n    0x824EA070,\n    0x824EA070,\n    0x824EA11C,\n    0x824EA028,\n    0x824EA1F0,\n    0x824EA264,\n]\n\n[[switch]]\nbase = 0x824EB47C\nr = 11\ndefault = 0x824EB74C\nlabels = [\n    0x824EB4D0,\n    0x824EB74C,\n    0x824EB74C,\n    0x824EB554,\n    0x824EB574,\n    0x824EB594,\n    0x824EB574,\n    0x824EB5E0,\n    0x824EB69C,\n    0x824EB6C0,\n    0x824EB6E4,\n    0x824EB6FC,\n    0x824EB714,\n    0x824EB730,\n    0x824EB744,\n]\n\n[[switch]]\nbase = 0x824EE648\nr = 11\ndefault = 0x824EE8E4\nlabels = [\n    0x824EE730,\n    0x824EE900,\n    0x824EE6F0,\n    0x824EEA34,\n    0x824EE8E4,\n    0x824EEAB0,\n    0x824EEAB0,\n    0x824EE8E4,\n    0x824EE8D0,\n    0x824EEC18,\n    0x824EEC18,\n    0x824EEC28,\n    0x824EEC28,\n    0x824EE8E4,\n    0x824EE8D0,\n    0x824EECCC,\n    0x824EECCC,\n    0x824EECE4,\n    0x824EE8E4,\n    0x824EE8D0,\n    0x824EECF8,\n    0x824EECF8,\n    0x824EED10,\n    0x824EE8E4,\n    0x824EE8D0,\n    0x824EECA0,\n    0x824EECA0,\n    0x824EECB8,\n    0x824EE8E4,\n    0x824EE8D0,\n    0x824EED24,\n    0x824EED24,\n    0x824EED34,\n    0x824EED34,\n    0x824EE8E4,\n    0x824EE8D0,\n]\n\n[[switch]]\nbase = 0x824EF894\nr = 10\ndefault = 0x824F059C\nlabels = [\n    0x824EF910,\n    0x824F00F8,\n    0x824F0488,\n    0x824F0394,\n    0x824EF8C0,\n]\n\n[[switch]]\nbase = 0x824F2240\nr = 11\ndefault = 0x824F2AB8\nlabels = [\n    0x824F22B4,\n    0x824F2358,\n    0x824F2AB8,\n    0x824F23C8,\n    0x824F2AB8,\n    0x824F2464,\n    0x824F2464,\n    0x824F24E0,\n    0x824F2340,\n    0x824F257C,\n    0x824F257C,\n    0x824F263C,\n    0x824F26D8,\n    0x824F2774,\n    0x824F2774,\n    0x824F2A14,\n    0x824F2828,\n    0x824F28C4,\n    0x824F28C4,\n    0x824F2AB8,\n    0x824F2970,\n    0x824F2A0C,\n    0x824F2A0C,\n]\n\n[[switch]]\nbase = 0x824F5344\nr = 11\ndefault = 0x824F5A34\nlabels = [\n    0x824F5380,\n    0x824F544C,\n    0x824F555C,\n    0x824F5628,\n    0x824F56F4,\n    0x824F57C0,\n    0x824F588C,\n    0x824F5898,\n    0x824F5960,\n]\n\n[[switch]]\nbase = 0x824F5ABC\nr = 11\ndefault = 0x824F7460\nlabels = [\n    0x824F5B10,\n    0x824F5C4C,\n    0x824F5F54,\n    0x824F61F0,\n    0x824F6420,\n    0x824F65EC,\n    0x824F6804,\n    0x824F69D0,\n    0x824F6D60,\n    0x824F6F90,\n    0x824F7260,\n    0x824F72C4,\n    0x824F7328,\n    0x824F738C,\n    0x824F73F0,\n]\n\n[[switch]]\nbase = 0x824F9DD4\nr = 11\ndefault = 0x824F9FB8\nlabels = [\n    0x824F9E08,\n    0x824F9ED8,\n    0x824F9EFC,\n    0x824F9F88,\n    0x824F9EA8,\n    0x824F9E90,\n    0x824F9EC0,\n]\n\n[[switch]]\nbase = 0x824FA918\nr = 10\ndefault = 0x824FAABC\nlabels = [\n    0x824FA948,\n    0x824FA9A0,\n    0x824FAAD8,\n    0x824FAB00,\n    0x824FAC3C,\n    0x824FAC64,\n]\n\n[[switch]]\nbase = 0x824FB400\nr = 10\ndefault = 0x824FB6D0\nlabels = [\n    0x824FB46C,\n    0x824FB488,\n    0x824FB4A4,\n    0x824FB4C0,\n    0x824FB518,\n    0x824FB4DC,\n    0x824FB534,\n    0x824FB550,\n    0x824FB56C,\n    0x824FB588,\n    0x824FB6D0,\n    0x824FB5A4,\n    0x824FB5C0,\n    0x824FB5DC,\n    0x824FB5F8,\n    0x824FB614,\n    0x824FB630,\n    0x824FB64C,\n    0x824FB668,\n    0x824FB684,\n    0x824FB6A0,\n]\n\n[[switch]]\nbase = 0x824FC4A4\nr = 11\ndefault = 0x824FC600\nlabels = [\n    0x824FC4D0,\n    0x824FC4DC,\n    0x824FC4E8,\n    0x824FC4F4,\n    0x824FC500,\n]\n\n[[switch]]\nbase = 0x824FC53C\nr = 11\ndefault = 0x824FC600\nlabels = [\n    0x824FC578,\n    0x824FC584,\n    0x824FC590,\n    0x824FC59C,\n    0x824FC5A8,\n    0x824FC5B4,\n    0x824FC5C0,\n    0x824FC5CC,\n    0x824FC5D8,\n]\n\n[[switch]]\nbase = 0x824FC644\nr = 10\ndefault = 0x824FC7A4\nlabels = [\n    0x824FC674,\n    0x824FC6A0,\n    0x824FC6D8,\n    0x824FC704,\n    0x824FC730,\n    0x824FC768,\n]\n\n[[switch]]\nbase = 0x824FE078\nr = 11\ndefault = 0x824FE0EC\nlabels = [\n    0x824FE0A4,\n    0x824FE0BC,\n    0x824FE0D4,\n    0x824FE0EC,\n    0x824FE0E0,\n]\n\n[[switch]]\nbase = 0x824FE128\nr = 30\ndefault = 0x824FE1D4\nlabels = [\n    0x824FE150,\n    0x824FE168,\n    0x824FE180,\n    0x824FE198,\n]\n\n[[switch]]\nbase = 0x824FE230\nr = 30\ndefault = 0x824FE2DC\nlabels = [\n    0x824FE258,\n    0x824FE270,\n    0x824FE288,\n    0x824FE2A0,\n]\n\n[[switch]]\nbase = 0x824FE570\nr = 11\ndefault = 0x824FEC90\nlabels = [\n    0x824FE5A0,\n    0x824FE690,\n    0x824FE800,\n    0x824FE940,\n    0x824FEA7C,\n    0x824FEBC0,\n]\n\n[[switch]]\nbase = 0x824FECC0\nr = 11\ndefault = 0x824FF2F4\nlabels = [\n    0x824FECEC,\n    0x824FEE08,\n    0x824FEE90,\n    0x824FF060,\n    0x824FF290,\n]\n\n[[switch]]\nbase = 0x824FFD28\nr = 11\ndefault = 0x8250502C\nlabels = [\n    0x824FFE40,\n    0x824FFE94,\n    0x824FFF40,\n    0x82500390,\n    0x825004E0,\n    0x8250065C,\n    0x825006DC,\n    0x825005DC,\n    0x825014AC,\n    0x82501548,\n    0x82501784,\n    0x825017FC,\n    0x82501850,\n    0x825018D0,\n    0x82501AD4,\n    0x82501B34,\n    0x82501DE0,\n    0x82501E48,\n    0x82502044,\n    0x825020B8,\n    0x82502248,\n    0x825022C4,\n    0x82500AB0,\n    0x82500B30,\n    0x82500DB4,\n    0x82500E34,\n    0x82501078,\n    0x82501190,\n    0x82501210,\n    0x825024CC,\n    0x82502564,\n    0x82502848,\n    0x82502464,\n    0x825024CC,\n    0x82502564,\n    0x82502848,\n    0x82502984,\n    0x825029DC,\n    0x82502C68,\n    0x82502D84,\n    0x82503068,\n    0x82503094,\n    0x82503290,\n    0x82503444,\n    0x825035F8,\n    0x82503798,\n    0x825038A8,\n    0x8250398C,\n    0x82503A28,\n    0x82503B18,\n    0x82503C0C,\n    0x82503C48,\n    0x82503E9C,\n    0x825041D8,\n    0x82504354,\n    0x825040C8,\n    0x82504474,\n    0x82504610,\n    0x825046A4,\n    0x825047A4,\n    0x82504EAC,\n    0x82504F24,\n    0x82504F68,\n    0x82504FE0,\n]\n\n[[switch]]\nbase = 0x824FFF5C\nr = 11\ndefault = 0x8250502C\nlabels = [\n    0x824FFF84,\n    0x82500024,\n    0x825000C8,\n    0x825001B4,\n]\n\n[[switch]]\nbase = 0x82504938\nr = 28\ndefault = 0x82504990\nlabels = [\n    0x82504960,\n    0x82504968,\n    0x82504970,\n    0x82504978,\n]\n\n[[switch]]\nbase = 0x8250499C\nr = 28\ndefault = 0x82504A74\nlabels = [\n    0x825049C4,\n    0x825049F0,\n    0x82504A1C,\n    0x82504A48,\n]\n\n[[switch]]\nbase = 0x8250760C\nr = 11\ndefault = 0x825076D4\nlabels = [\n    0x82507634,\n    0x82507644,\n    0x825076FC,\n    0x82507650,\n]\n\n[[switch]]\nbase = 0x82507EAC\nr = 11\ndefault = 0x82508158\nlabels = [\n    0x82507EF0,\n    0x82508158,\n    0x82508158,\n    0x82508158,\n    0x82508158,\n    0x82508158,\n    0x8250803C,\n    0x82508114,\n    0x825080AC,\n    0x82508158,\n    0x8250813C,\n]\n\n[[switch]]\nbase = 0x825098AC\nr = 11\ndefault = 0x82509F98\nlabels = [\n    0x825098F4,\n    0x82509B30,\n    0x82509B98,\n    0x82509D8C,\n    0x82509DE0,\n    0x82509E30,\n    0x82509E3C,\n    0x82509F44,\n    0x82509F60,\n    0x82509F6C,\n    0x82509F6C,\n    0x82509F6C,\n]\n\n[[switch]]\nbase = 0x8250A69C\nr = 11\ndefault = 0x8250A7FC\nlabels = [\n    0x8250A6C4,\n    0x8250A6D4,\n    0x8250A824,\n    0x8250A6E0,\n]\n\n[[switch]]\nbase = 0x8250B1FC\nr = 11\ndefault = 0x8250BBE8\nlabels = [\n    0x8250B23C,\n    0x8250B534,\n    0x8250B6AC,\n    0x8250B8B0,\n    0x8250BAE8,\n    0x8250BB7C,\n    0x8250BBE8,\n    0x8250BBE8,\n    0x8250BBE8,\n    0x8250BC64,\n]\n\n[[switch]]\nbase = 0x8250C43C\nr = 11\ndefault = 0x8250D3EC\nlabels = [\n    0x8250C4BC,\n    0x8250C56C,\n    0x8250C6A0,\n    0x8250C6E8,\n    0x8250C7D0,\n    0x8250C8BC,\n    0x8250C99C,\n    0x8250CA14,\n    0x8250CA60,\n    0x8250CB68,\n    0x8250CC64,\n    0x8250CCDC,\n    0x8250CD28,\n    0x8250D3EC,\n    0x8250CE24,\n    0x8250CE9C,\n    0x8250CEE8,\n    0x8250CFD8,\n    0x8250D050,\n    0x8250D0CC,\n    0x8250D1C8,\n    0x8250D28C,\n    0x8250D388,\n    0x8250D388,\n    0x8250D3EC,\n    0x8250D3E4,\n]\n\n[[switch]]\nbase = 0x8250D6E4\nr = 11\ndefault = 0x8250D808\nlabels = [\n    0x8250D70C,\n    0x8250D71C,\n    0x8250D830,\n    0x8250D728,\n]\n\n[[switch]]\nbase = 0x8250DFE0\nr = 11\ndefault = 0x8250E898\nlabels = [\n    0x8250E020,\n    0x8250E218,\n    0x8250E454,\n    0x8250E5D4,\n    0x8250E768,\n    0x8250E814,\n    0x8250E898,\n    0x8250E898,\n    0x8250E898,\n    0x8250E908,\n]\n\n[[switch]]\nbase = 0x8250F13C\nr = 11\ndefault = 0x8250F35C\nlabels = [\n    0x8250F17C,\n    0x8250F288,\n    0x8250F35C,\n    0x8250F35C,\n    0x8250F35C,\n    0x8250F300,\n    0x8250F35C,\n    0x8250F35C,\n    0x8250F35C,\n    0x8250F3D8,\n]\n\n[[switch]]\nbase = 0x8250F454\nr = 11\ndefault = 0x0\nlabels = [\n    0x8250F488,\n    0x8250F4A0,\n    0x8250F4DC,\n    0x8250F4DC,\n    0x8250F4DC,\n    0x8250F4C8,\n    0x8250F4D4,\n]\n\n[[switch]]\nbase = 0x82510ECC\nr = 11\ndefault = 0x825114BC\nlabels = [\n    0x82510EF4,\n    0x82510F3C,\n    0x82511280,\n    0x82511404,\n]\n\n[[switch]]\nbase = 0x82511CC4\nr = 4\ndefault = 0x82512240\nlabels = [\n    0x82511D18,\n    0x82511D38,\n    0x82511D5C,\n    0x82511D80,\n    0x82511DA4,\n    0x82511E14,\n    0x82511E38,\n    0x82511E64,\n    0x82511D80,\n    0x82511EBC,\n    0x82511F48,\n    0x825120A8,\n    0x82511F9C,\n    0x825120E0,\n    0x82512140,\n]\n\n[[switch]]\nbase = 0x825126EC\nr = 11\ndefault = 0x82512E48\nlabels = [\n    0x82512740,\n    0x82512770,\n    0x82512830,\n    0x825129F8,\n    0x82512A80,\n    0x82512AE4,\n    0x82512B24,\n    0x82512C7C,\n    0x82512C7C,\n    0x82512E48,\n    0x82512E48,\n    0x82512D2C,\n    0x82512CA0,\n    0x82512D94,\n    0x82512DF4,\n]\n\n[[switch]]\nbase = 0x8251ADE8\nr = 11\ndefault = 0x8251B1C8\nlabels = [\n    0x8251AE14,\n    0x8251AE5C,\n    0x8251AF04,\n    0x8251AE14,\n    0x8251AE14,\n]\n\n[[switch]]\nbase = 0x8252C6A4\nr = 10\ndefault = 0x8252C78C\nlabels = [\n    0x8252C6D4,\n    0x8252C7A8,\n    0x8252C954,\n    0x8252C9D0,\n    0x8252CAC0,\n    0x8252CAE4,\n]\n\n[[switch]]\nbase = 0x82568A08\nr = 4\ndefault = 0x82568AF4\nlabels = [\n    0x82568A54,\n    0x82568A68,\n    0x82568A74,\n    0x82568A80,\n    0x82568A8C,\n    0x82568A98,\n    0x82568AA4,\n    0x82568AB0,\n    0x82568ABC,\n    0x82568AC8,\n    0x82568AD4,\n    0x82568AE0,\n    0x82568AEC,\n]\n\n[[switch]]\nbase = 0x8256E4B8\nr = 10\ndefault = 0x8256E4F4\nlabels = [\n    0x8256E4E8,\n    0x8256E4E8,\n    0x8256E4F4,\n    0x8256E4E8,\n    0x8256E4F4,\n    0x8256E4E8,\n]\n\n[[switch]]\nbase = 0x82587288\nr = 11\ndefault = 0x82587310\nlabels = [\n    0x825872C4,\n    0x825872CC,\n    0x825872D4,\n    0x825872DC,\n    0x825872E4,\n    0x825872EC,\n    0x825872F4,\n    0x825872FC,\n    0x82587304,\n]\n\n[[switch]]\nbase = 0x82587C58\nr = 11\ndefault = 0x82588A00\nlabels = [\n    0x82587CC4,\n    0x8258800C,\n    0x82588144,\n    0x82587D8C,\n    0x82587ED8,\n    0x825885B0,\n    0x82588A00,\n    0x82588A00,\n    0x82588394,\n    0x82588A00,\n    0x82588408,\n    0x82588A00,\n    0x82588A00,\n    0x82588A00,\n    0x82588A00,\n    0x82588A00,\n    0x82588A00,\n    0x82588A00,\n    0x8258834C,\n    0x82588A00,\n    0x8258849C,\n]\n\n[[switch]]\nbase = 0x8259A654\nr = 11\ndefault = 0x8259A6C8\nlabels = [\n    0x8259A680,\n    0x8259A68C,\n    0x8259A6A0,\n    0x8259A6B8,\n    0x8259A6B8,\n]\n\n[[switch]]\nbase = 0x825B0BF4\nr = 11\ndefault = 0x825B0CB4\nlabels = [\n    0x825B0C24,\n    0x825B0C3C,\n    0x825B0C54,\n    0x825B0C6C,\n    0x825B0C84,\n    0x825B0C9C,\n]\n\n[[switch]]\nbase = 0x825B0D00\nr = 11\ndefault = 0x825B1160\nlabels = [\n    0x825B117C,\n    0x825B1160,\n    0x825B1110,\n    0x825B1110,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1160,\n    0x825B1110,\n]\n\n[[switch]]\nbase = 0x825B2420\nr = 4\ndefault = 0x825B2640\nlabels = [\n    0x825B2474,\n    0x825B2488,\n    0x825B249C,\n    0x825B24B8,\n    0x825B24C8,\n    0x825B2540,\n    0x825B2560,\n    0x825B24D8,\n    0x825B24F0,\n    0x825B2508,\n    0x825B2578,\n    0x825B2590,\n    0x825B25C4,\n    0x825B2604,\n    0x825B2614,\n]\n\n[[switch]]\nbase = 0x825B3A34\nr = 11\ndefault = 0x825B3AA4\nlabels = [\n    0x825B3AA4,\n    0x825B3A6C,\n    0x825B3A74,\n    0x825B3A7C,\n    0x825B3A84,\n    0x825B3A8C,\n    0x825B3A94,\n    0x825B3A9C,\n]\n\n[[switch]]\nbase = 0x825B4120\nr = 11\ndefault = 0x0\nlabels = [\n    0x825B4154,\n    0x825B415C,\n    0x825B4154,\n    0x825B4164,\n    0x825B4164,\n    0x825B4164,\n    0x825B4164,\n]\n\n[[switch]]\nbase = 0x825B4500\nr = 3\ndefault = 0x825B47A0\nlabels = [\n    0x825B4538,\n    0x825B467C,\n    0x825B467C,\n    0x825B4628,\n    0x825B46D0,\n    0x825B4734,\n    0x825B47A0,\n    0x825B45A0,\n]\n\n[[switch]]\nbase = 0x825C396C\nr = 11\ndefault = 0x825C3A6C\nlabels = [\n    0x825C39A0,\n    0x825C39B0,\n    0x825C39C0,\n    0x825C39D0,\n    0x825C39E8,\n    0x825C39FC,\n    0x825C3A34,\n]\n\n[[switch]]\nbase = 0x825C6678\nr = 10\ndefault = 0x825C69B0\nlabels = [\n    0x825C66A0,\n    0x825C67D0,\n    0x825C688C,\n    0x825C692C,\n]\n\n[[switch]]\nbase = 0x825E923C\nr = 4\ndefault = 0x825E92B8\nlabels = [\n    0x825E9270,\n    0x825E9284,\n    0x825E9270,\n    0x825E9284,\n    0x825E9284,\n    0x825E9284,\n    0x825E9284,\n]\n\n[[switch]]\nbase = 0x8260F808\nr = 3\ndefault = 0x8260F8A8\nlabels = [\n    0x8260F880,\n    0x8260F830,\n    0x8260F858,\n    0x8260F880,\n]\n\n[[switch]]\nbase = 0x8261042C\nr = 11\ndefault = 0x82610464\nlabels = [\n    0x82610454,\n    0x8261045C,\n    0x826103FC,\n    0x826103FC,\n]\n\n[[switch]]\nbase = 0x8261089C\nr = 11\ndefault = 0x826108D4\nlabels = [\n    0x826108C4,\n    0x826108CC,\n    0x8261086C,\n    0x8261086C,\n]\n\n[[switch]]\nbase = 0x826160B4\nr = 11\ndefault = 0x826162F4\nlabels = [\n    0x826160E0,\n    0x82616240,\n    0x826162A0,\n    0x826162F4,\n    0x82616360,\n]\n\n[[switch]]\nbase = 0x82635CE0\nr = 11\ndefault = 0x82635EDC\nlabels = [\n    0x82635D1C,\n    0x82635D54,\n    0x82635DC0,\n    0x82635DF8,\n    0x82635E64,\n    0x82635E7C,\n    0x82635E94,\n    0x82635EAC,\n    0x82635EC4,\n]\n\n[[switch]]\nbase = 0x82636934\nr = 11\ndefault = 0x826369E0\nlabels = [\n    0x82636988,\n    0x826369E0,\n    0x826369A0,\n    0x826369E0,\n    0x82636994,\n    0x826369E0,\n    0x826369AC,\n    0x826369E0,\n    0x826369B8,\n    0x826369E0,\n    0x826369C4,\n    0x826369E0,\n    0x826369D0,\n    0x826369E0,\n    0x826369DC,\n]\n\n[[switch]]\nbase = 0x82645724\nr = 30\ndefault = 0x82645790\nlabels = [\n    0x82645764,\n    0x8264577C,\n    0x82645784,\n    0x8264574C,\n]\n\n[[switch]]\nbase = 0x8264C7D8\nr = 11\ndefault = 0x8264C828\nlabels = [\n    0x8264C810,\n    0x8264C818,\n    0x8264C820,\n    0x8264C820,\n    0x8264C820,\n    0x8264C810,\n    0x8264C828,\n    0x8264C810,\n]\n\n[[switch]]\nbase = 0x826555C8\nr = 6\ndefault = 0x8265584C\nlabels = [\n    0x82655600,\n    0x82655634,\n    0x826556D8,\n    0x82655720,\n    0x82655768,\n    0x826557B0,\n    0x826557D0,\n    0x82655818,\n]\n\n[[switch]]\nbase = 0x826559B0\nr = 10\ndefault = 0x82655BD4\nlabels = [\n    0x82655B14,\n    0x82655BD4,\n    0x82655B2C,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BB4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BCC,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655BD4,\n    0x82655B44,\n]\n\n[[switch]]\nbase = 0x8265D3C8\nr = 10\ndefault = 0x8265D510\nlabels = [\n    0x8265D3FC,\n    0x8265D46C,\n    0x8265D510,\n    0x8265D488,\n    0x8265D430,\n    0x8265D430,\n    0x8265D4EC,\n]\n\n[[switch]]\nbase = 0x8266953C\nr = 4\ndefault = 0x826695E4\nlabels = [\n    0x82669564,\n    0x82669570,\n    0x8266957C,\n    0x82669588,\n]\n\n[[switch]]\nbase = 0x8266FF6C\nr = 3\ndefault = 0x82670158\nlabels = [\n    0x8266FFA0,\n    0x8266FFE0,\n    0x82670014,\n    0x8267004C,\n    0x82670084,\n    0x826700C8,\n    0x8267010C,\n]\n\n[[switch]]\nbase = 0x82671B3C\nr = 11\ndefault = 0x82671E64\nlabels = [\n    0x82671BAC,\n    0x82671BC0,\n    0x82671E64,\n    0x82671BCC,\n    0x82671BFC,\n    0x82671C7C,\n    0x82671C58,\n    0x82671E64,\n    0x82671E64,\n    0x82671C30,\n    0x82671C0C,\n    0x82671C94,\n    0x82671CB0,\n    0x82671DB0,\n    0x82671DD8,\n    0x82671E00,\n    0x82671E28,\n    0x82671D20,\n    0x82671D44,\n    0x82671D68,\n    0x82671D8C,\n    0x82671E50,\n]\n\n[[switch]]\nbase = 0x82676764\nr = 11\ndefault = 0x82676838\nlabels = [\n    0x82676828,\n    0x82676838,\n    0x826767F8,\n    0x826767E8,\n    0x82676838,\n    0x82676818,\n    0x82676838,\n    0x82676838,\n    0x82676838,\n    0x826767D8,\n    0x82676838,\n    0x826767C8,\n    0x82676838,\n    0x82676838,\n    0x82676838,\n    0x82676838,\n    0x82676838,\n    0x82676838,\n    0x82676808,\n]\n\n[[switch]]\nbase = 0x82677708\nr = 11\ndefault = 0x82677898\nlabels = [\n    0x82677884,\n    0x8267773C,\n    0x8267774C,\n    0x826777A8,\n    0x82677778,\n    0x826777B8,\n    0x826777F8,\n]\n\n[[switch]]\nbase = 0x8271695C\nr = 11\ndefault = 0x82716A24\nlabels = [\n    0x827169A0,\n    0x827169D0,\n    0x82716A04,\n    0x82716A0C,\n    0x82716A0C,\n    0x82716A0C,\n    0x82716A0C,\n    0x82716A0C,\n    0x82716A0C,\n    0x82716A24,\n    0x82716A0C,\n]\n\n[[switch]]\nbase = 0x82718A74\nr = 10\ndefault = 0x82719010\nlabels = [\n    0x82718FDC,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82718EA0,\n    0x82718EA0,\n    0x82718EC0,\n    0x82718F20,\n    0x82718F44,\n    0x82718F20,\n    0x82718F20,\n    0x82718F20,\n    0x82718F20,\n    0x82718EE0,\n    0x82718F00,\n    0x82718F20,\n    0x82718F2C,\n    0x82718F20,\n    0x82718F20,\n    0x82718F20,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718FDC,\n    0x82718E84,\n    0x82719060,\n    0x82719040,\n    0x82718F5C,\n    0x82718F2C,\n    0x82718F74,\n    0x82719010,\n    0x82719010,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82718F8C,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82719010,\n    0x82718FB8,\n]\n\n[[switch]]\nbase = 0x82720BBC\nr = 11\ndefault = 0x82720C00\nlabels = [\n    0x82720BE8,\n    0x82720BF0,\n    0x82720BF0,\n    0x82720BF8,\n    0x82720BF8,\n]\n\n[[switch]]\nbase = 0x82727FA0\nr = 11\ndefault = 0x82728084\nlabels = [\n    0x82727FD8,\n    0x82727FE8,\n    0x82728084,\n    0x82727FF8,\n    0x82728084,\n    0x82728084,\n    0x82728084,\n    0x82728008,\n]\n\n[[switch]]\nbase = 0x82728014\nr = 11\ndefault = 0x82728078\nlabels = [\n    0x82728048,\n    0x82728058,\n    0x82728068,\n    0x82728048,\n    0x82728058,\n    0x82728048,\n    0x82728058,\n]\n\n[[switch]]\nbase = 0x82729BC4\nr = 11\ndefault = 0x82729C20\nlabels = [\n    0x82729BF0,\n    0x82729C00,\n    0x82729C00,\n    0x82729C10,\n    0x82729C10,\n]\n\n[[switch]]\nbase = 0x82733BB8\nr = 11\ndefault = 0x82733F8C\nlabels = [\n    0x82733F8C,\n    0x82733BE0,\n    0x82733DE0,\n    0x82733E80,\n]\n\n[[switch]]\nbase = 0x82735084\nr = 11\ndefault = 0x82735150\nlabels = [\n    0x82735100,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x8273510C,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735118,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735150,\n    0x82735118,\n]\n\n[[switch]]\nbase = 0x8273BF84\nr = 11\ndefault = 0x8273C0D8\nlabels = [\n    0x8273C060,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C088,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0A0,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0D8,\n    0x8273C0B8,\n]\n\n[[switch]]\nbase = 0x8273C124\nr = 11\ndefault = 0x8273C220\nlabels = [\n    0x8273C1AC,\n    0x8273C1BC,\n    0x8273C1CC,\n    0x8273C1DC,\n    0x8273C1E8,\n    0x8273C1F4,\n    0x8273C208,\n    0x8273C214,\n    0x8273C16C,\n    0x8273C194,\n    0x8273C200,\n    0x8273C1A0,\n]\n\n[[switch]]\nbase = 0x8273C370\nr = 3\ndefault = 0x8273C430\nlabels = [\n    0x8273C3C0,\n    0x8273C3C8,\n    0x8273C3D0,\n    0x8273C3D8,\n    0x8273C3E0,\n    0x8273C3E8,\n    0x8273C3F0,\n    0x8273C3F8,\n    0x8273C400,\n    0x8273C408,\n    0x8273C410,\n    0x8273C418,\n    0x8273C420,\n    0x8273C428,\n]\n\n[[switch]]\nbase = 0x8273C500\nr = 11\ndefault = 0x8273C8C0\nlabels = [\n    0x8273C7CC,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C7D4,\n    0x8273C8C0,\n    0x8273C7DC,\n    0x8273C7DC,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C7E4,\n    0x8273C7E4,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C7C4,\n]\n\n[[switch]]\nbase = 0x8273C814\nr = 11\ndefault = 0x8273C8C0\nlabels = [\n    0x8273C84C,\n    0x8273C8C0,\n    0x8273C8C0,\n    0x8273C854,\n    0x8273C85C,\n    0x8273C864,\n    0x8273C86C,\n    0x8273C874,\n]\n\n[[switch]]\nbase = 0x8273C9DC\nr = 10\ndefault = 0x8273CD10\nlabels = [\n    0x8273CD18,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CCB8,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CCC4,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CCCC,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CCD4,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD18,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD18,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CCDC,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CCF0,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD18,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD18,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD10,\n    0x8273CD18,\n]\n\n[[switch]]\nbase = 0x8273D448\nr = 11\ndefault = 0x8273D5DC\nlabels = [\n    0x8273D470,\n    0x8273D480,\n    0x8273D470,\n    0x8273D4A0,\n]\n\n[[switch]]\nbase = 0x8273D524\nr = 11\ndefault = 0x8273D5DC\nlabels = [\n    0x8273D54C,\n    0x8273D470,\n    0x8273D56C,\n    0x8273D470,\n]\n\n[[switch]]\nbase = 0x8273EA04\nr = 11\ndefault = 0x8273EB80\nlabels = [\n    0x8273EA34,\n    0x8273EA90,\n    0x8273EAF4,\n    0x8273EB80,\n    0x8273EB48,\n    0x8273EB5C,\n]\n\n[[switch]]\nbase = 0x8273FFB0\nr = 11\ndefault = 0x827401C8\nlabels = [\n    0x8273FFFC,\n    0x8274009C,\n    0x827400BC,\n    0x82740154,\n]\n\n[[switch]]\nbase = 0x82740400\nr = 11\ndefault = 0x8274051C\nlabels = [\n    0x8274051C,\n    0x82740428,\n    0x827404D8,\n    0x8274051C,\n]\n\n[[switch]]\nbase = 0x82741200\nr = 11\ndefault = 0x8274124C\nlabels = [\n    0x82741234,\n    0x82741240,\n    0x8274124C,\n    0x82741228,\n]\n\n[[switch]]\nbase = 0x82748850\nr = 9\ndefault = 0x82748C60\nlabels = [\n    0x82748B68,\n    0x827489E4,\n    0x82748878,\n    0x82748878,\n]\n\n[[switch]]\nbase = 0x82749868\nr = 10\ndefault = 0x82749778\nlabels = [\n    0x82749890,\n    0x82749890,\n    0x827498B8,\n    0x827498EC,\n]\n\n[[switch]]\nbase = 0x82749D80\nr = 11\ndefault = 0x82749DD0\nlabels = [\n    0x82749DA8,\n    0x82749DB4,\n    0x82749DC0,\n    0x82749DA8,\n]\n\n[[switch]]\nbase = 0x82749F3C\nr = 11\ndefault = 0x82749FD4\nlabels = [\n    0x82749F64,\n    0x82749F7C,\n    0x82749FA4,\n    0x82749F64,\n]\n\n[[switch]]\nbase = 0x8274CD9C\nr = 11\ndefault = 0x8274CD90\nlabels = [\n    0x8274D108,\n    0x8274CD90,\n    0x8274D128,\n    0x8274D1EC,\n    0x8274CDDC,\n    0x8274CEA0,\n    0x8274CF54,\n    0x8274CF94,\n    0x8274CFD8,\n    0x8274D098,\n]\n\n[[switch]]\nbase = 0x8274EC8C\nr = 11\ndefault = 0x8274F35C\nlabels = [\n    0x8274ECBC,\n    0x8274ED4C,\n    0x8274ED74,\n    0x8274F35C,\n    0x8274EE20,\n    0x8274EEDC,\n]\n\n[[switch]]\nbase = 0x8274FE04\nr = 11\ndefault = 0x82750694\nlabels = [\n    0x8274FE34,\n    0x8274FF9C,\n    0x82750178,\n    0x82750694,\n    0x8275067C,\n    0x827502E4,\n]\n\n[[switch]]\nbase = 0x82752E90\nr = 11\ndefault = 0x8275327C\nlabels = [\n    0x82752EC8,\n    0x82752F3C,\n    0x82752F44,\n    0x82752F4C,\n    0x8275327C,\n    0x82752F58,\n    0x82752F68,\n    0x82752F78,\n]\n\n[[switch]]\nbase = 0x8275A4B8\nr = 10\ndefault = 0x8275A584\nlabels = [\n    0x8275A4F0,\n    0x8275A4F0,\n    0x8275A4E0,\n    0x8275A51C,\n]\n\n[[switch]]\nbase = 0x8275A770\nr = 10\ndefault = 0x8275A8D0\nlabels = [\n    0x8275A798,\n    0x8275A7AC,\n    0x8275A798,\n    0x8275A7C8,\n]\n\n[[switch]]\nbase = 0x8275A848\nr = 10\ndefault = 0x8275A8D0\nlabels = [\n    0x8275A870,\n    0x8275A798,\n    0x8275A894,\n    0x8275A798,\n]\n\n[[switch]]\nbase = 0x8275AB88\nr = 11\ndefault = 0x8275AD40\nlabels = [\n    0x8275ABB0,\n    0x8275ABB0,\n    0x8275ABDC,\n    0x8275AC2C,\n]\n\n[[switch]]\nbase = 0x8275BE78\nr = 11\ndefault = 0x8275BE68\nlabels = [\n    0x8275BEB4,\n    0x8275C088,\n    0x8275C0E0,\n    0x8275C138,\n    0x8275C16C,\n    0x8275C214,\n    0x8275C3EC,\n    0x8275C9B8,\n    0x8275CE7C,\n]\n\n[[switch]]\nbase = 0x8275CFA8\nr = 11\ndefault = 0x8275CF9C\nlabels = [\n    0x8275D00C,\n    0x8275D00C,\n    0x8275D00C,\n    0x8275D00C,\n    0x8275D024,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D06C,\n    0x8275D054,\n    0x8275D054,\n    0x8275D054,\n    0x8275D0B0,\n    0x8275D0EC,\n]\n\n[[switch]]\nbase = 0x82763E30\nr = 5\ndefault = 0x82764034\nlabels = [\n    0x82764018,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82763FFC,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82763FE0,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82764034,\n    0x82763FC4,\n]\n\n[[switch]]\nbase = 0x8276554C\nr = 27\ndefault = 0x827656E4\nlabels = [\n    0x827657F8,\n    0x82765580,\n    0x82765590,\n    0x827655B4,\n    0x827655E8,\n    0x8276562C,\n    0x82765680,\n]\n\n[[switch]]\nbase = 0x82769E4C\nr = 11\ndefault = 0x8276A384\nlabels = [\n    0x8276A050,\n    0x82769E90,\n    0x8276A0A4,\n    0x8276A0D0,\n    0x8276A0E8,\n    0x8276A120,\n    0x8276A16C,\n    0x8276A1A8,\n    0x8276A1F8,\n    0x8276A22C,\n    0x8276A27C,\n]\n\n[[switch]]\nbase = 0x8276A44C\nr = 11\ndefault = 0x8276BB9C\nlabels = [\n    0x8276A538,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276AFF0,\n    0x8276B5B4,\n    0x8276B658,\n    0x8276B680,\n    0x8276B680,\n    0x8276B6A4,\n    0x8276B874,\n    0x8276BB9C,\n    0x8276A8C4,\n    0x8276AA0C,\n    0x8276A994,\n    0x8276AA68,\n    0x8276AAE8,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276AD84,\n    0x8276B060,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276B0D0,\n    0x8276B4A0,\n    0x8276B4E8,\n    0x8276AB04,\n    0x8276B148,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276ACAC,\n    0x8276AE40,\n    0x8276AE08,\n    0x8276AC84,\n    0x8276ABE4,\n    0x8276AC2C,\n    0x8276AC58,\n    0x8276BB9C,\n    0x8276AEDC,\n    0x8276AF94,\n    0x8276AF38,\n    0x8276BB9C,\n    0x8276ACD8,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276BB9C,\n    0x8276B35C,\n]\n\n[[switch]]\nbase = 0x8276C638\nr = 11\ndefault = 0x8276D700\nlabels = [\n    0x8276C720,\n    0x8276D334,\n    0x8276C8E4,\n    0x8276C990,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276C96C,\n    0x8276C938,\n    0x8276C9B8,\n    0x8276C9E4,\n    0x8276CA30,\n    0x8276CA7C,\n    0x8276CB18,\n    0x8276CB64,\n    0x8276CBB4,\n    0x8276CBEC,\n    0x8276CC88,\n    0x8276CD4C,\n    0x8276CE48,\n    0x8276D700,\n    0x8276D700,\n    0x8276D3C4,\n    0x8276CE74,\n    0x8276D2C0,\n    0x8276D2E4,\n    0x8276D700,\n    0x8276D49C,\n    0x8276D468,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D700,\n    0x8276D4E0,\n    0x8276D700,\n    0x8276D530,\n    0x8276D588,\n    0x8276D700,\n    0x8276D450,\n    0x8276D698,\n    0x8276D6D0,\n]\n\n[[switch]]\nbase = 0x8276CEBC\nr = 11\ndefault = 0x8276D294\nlabels = [\n    0x8276CEE4,\n    0x8276CF20,\n    0x8276CFB0,\n    0x8276D03C,\n]\n\n[[switch]]\nbase = 0x8276DB20\nr = 11\ndefault = 0x8276DFB4\nlabels = [\n    0x8276DB64,\n    0x8276DFB4,\n    0x8276DB68,\n    0x8276DBA8,\n    0x8276DFB4,\n    0x8276DFB4,\n    0x8276DFB4,\n    0x8276DFB4,\n    0x8276DFB4,\n    0x8276DBF4,\n    0x8276DC38,\n]\n\n[[switch]]\nbase = 0x8276DD68\nr = 11\ndefault = 0x8276DF40\nlabels = [\n    0x8276DD90,\n    0x8276DDBC,\n    0x8276DDF0,\n    0x8276DE80,\n]\n\n[[switch]]\nbase = 0x8276E050\nr = 11\ndefault = 0x8276E770\nlabels = [\n    0x8276E088,\n    0x8276E770,\n    0x8276E770,\n    0x8276E0C4,\n    0x8276E770,\n    0x8276E770,\n    0x8276E258,\n    0x8276E5E8,\n]\n\n[[switch]]\nbase = 0x82772908\nr = 11\ndefault = 0x82772CD8\nlabels = [\n    0x82772944,\n    0x82772A28,\n    0x82772A60,\n    0x82772B04,\n    0x82772B8C,\n    0x82772C10,\n    0x82772C10,\n    0x82772CD8,\n    0x82772C5C,\n]\n\n[[switch]]\nbase = 0x82772D90\nr = 11\ndefault = 0x82772F9C\nlabels = [\n    0x82772DB8,\n    0x82772E30,\n    0x82772E7C,\n    0x82772F10,\n]\n\n[[switch]]\nbase = 0x82773290\nr = 11\ndefault = 0x82773284\nlabels = [\n    0x827732C0,\n    0x82773364,\n    0x82773380,\n    0x8277339C,\n    0x827733B8,\n    0x827733D8,\n]\n\n[[switch]]\nbase = 0x82773578\nr = 11\ndefault = 0x82773568\nlabels = [\n    0x827735A0,\n    0x827736BC,\n    0x8277368C,\n    0x827736F8,\n]\n\n[[switch]]\nbase = 0x82773A00\nr = 11\ndefault = 0x827739F0\nlabels = [\n    0x82773A54,\n    0x82773B30,\n    0x82773BD0,\n    0x82773C5C,\n    0x82773CBC,\n    0x827739F0,\n    0x827739F0,\n    0x827739F0,\n    0x827739F0,\n    0x827739F0,\n    0x827739F0,\n    0x827739F0,\n    0x827739F0,\n    0x82773D84,\n    0x82773E18,\n]\n\n[[switch]]\nbase = 0x827744FC\nr = 11\ndefault = 0x82774BF0\nlabels = [\n    0x82774580,\n    0x82774BF0,\n    0x82774BF0,\n    0x82774BF0,\n    0x82774530,\n    0x82774934,\n    0x82774B1C,\n]\n\n[[switch]]\nbase = 0x82775018\nr = 3\ndefault = 0x827750D8\nlabels = [\n    0x82775060,\n    0x82775070,\n    0x82775078,\n    0x82775084,\n    0x8277509C,\n    0x82775090,\n    0x827750A8,\n    0x827750B4,\n    0x827750C0,\n    0x82775068,\n    0x82775060,\n    0x827750CC,\n]\n\n[[switch]]\nbase = 0x827863A0\nr = 11\ndefault = 0x82786510\nlabels = [\n    0x8278643C,\n    0x82786458,\n    0x82786474,\n    0x82786490,\n    0x827864A4,\n    0x827864B8,\n    0x827864E8,\n    0x827864FC,\n    0x82786404,\n    0x82786420,\n    0x827864D0,\n    0x827863E8,\n]\n\n[[switch]]\nbase = 0x82799D4C\nr = 11\ndefault = 0x82799FB8\nlabels = [\n    0x82799FB0,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799E80,\n    0x82799E40,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799FB8,\n    0x82799F80,\n    0x82799F50,\n    0x82799F20,\n    0x82799EF0,\n    0x82799EC0,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n    0x8279A130,\n]\n\n[[switch]]\nbase = 0x8279A550\nr = 11\ndefault = 0x8279A968\nlabels = [\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A6F4,\n    0x8279A6C8,\n    0x8279A69C,\n    0x8279A670,\n    0x8279A644,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n    0x8279A968,\n]\n\n[[switch]]\nbase = 0x8279CBFC\nr = 28\ndefault = 0x8279CFB8\nlabels = [\n    0x8279CC34,\n    0x8279CC3C,\n    0x8279CC54,\n    0x8279CCA8,\n    0x8279CCC4,\n    0x8279CCE0,\n    0x8279CDF4,\n    0x8279CF08,\n]\n\n[[switch]]\nbase = 0x8279D804\nr = 4\ndefault = 0x8279D988\nlabels = [\n    0x8279D834,\n    0x8279D874,\n    0x8279D8B8,\n    0x8279D8E4,\n    0x8279D90C,\n    0x8279D94C,\n]\n\n[[switch]]\nbase = 0x827B37B4\nr = 11\ndefault = 0x827B3934\nlabels = [\n    0x827B3808,\n    0x827B38AC,\n    0x827B38D4,\n    0x827B3814,\n    0x827B3820,\n    0x827B3848,\n    0x827B3854,\n    0x827B3860,\n    0x827B3868,\n    0x827B3890,\n    0x827B38B8,\n    0x827B38E0,\n    0x827B38E8,\n    0x827B3910,\n    0x827B3918,\n]\n\n[[switch]]\nbase = 0x827B396C\nr = 11\ndefault = 0x827B3C0C\nlabels = [\n    0x827B39C0,\n    0x827B3B28,\n    0x827B3B6C,\n    0x827B39F0,\n    0x827B3A18,\n    0x827B3A68,\n    0x827B3A90,\n    0x827B3AB8,\n    0x827B3AC0,\n    0x827B3B0C,\n    0x827B3B50,\n    0x827B3B94,\n    0x827B3B9C,\n    0x827B3BE8,\n    0x827B3BF0,\n]\n\n[[switch]]\nbase = 0x827C301C\nr = 19\ndefault = 0x827C2F80\nlabels = [\n    0x827C3088,\n    0x827C30C8,\n    0x827C2F80,\n    0x827C30E0,\n    0x827C30E0,\n    0x827C3120,\n    0x827C30E0,\n    0x827C3208,\n    0x827C3280,\n    0x827C3240,\n    0x827C2F80,\n    0x827C2F80,\n    0x827C3280,\n    0x827C3280,\n    0x827C2F80,\n    0x827C2F80,\n    0x827C3280,\n    0x827C2F80,\n    0x827C3280,\n    0x827C32C0,\n    0x827C32D8,\n]\n\n[[switch]]\nbase = 0x827C3B04\nr = 11\ndefault = 0x827C3ED8\nlabels = [\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3ED8,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3BE0,\n    0x827C3ED8,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3ED8,\n    0x827C3ED8,\n    0x827C3B70,\n    0x827C3ED8,\n    0x827C3B70,\n    0x827C3B70,\n    0x827C3B70,\n]\n\n[[switch]]\nbase = 0x827D5D10\nr = 4\ndefault = 0x827D6EA4\nlabels = [\n    0x827D5D58,\n    0x827D65AC,\n    0x827D6724,\n    0x827D682C,\n    0x827D6078,\n    0x827D6938,\n    0x827D6C30,\n    0x827D69E0,\n    0x827D5F68,\n    0x827D6D38,\n    0x827D6228,\n    0x827D63E0,\n]\n\n[[switch]]\nbase = 0x827D9084\nr = 7\ndefault = 0x827D91F4\nlabels = [\n    0x827D90AC,\n    0x827D90F4,\n    0x827D914C,\n    0x827D91A0,\n]\n\n[[switch]]\nbase = 0x827D91FC\nr = 8\ndefault = 0x0\nlabels = [\n    0x827D9224,\n    0x827D9280,\n    0x827D92EC,\n    0x827D9354,\n]\n\n[[switch]]\nbase = 0x82803294\nr = 10\ndefault = 0x82803320\nlabels = [\n    0x828032BC,\n    0x828032C8,\n    0x828032DC,\n    0x828032F0,\n]\n\n[[switch]]\nbase = 0x8289E3A0\nr = 3\ndefault = 0x8289E638\nlabels = [\n    0x8289E3D4,\n    0x8289E40C,\n    0x8289E480,\n    0x8289E528,\n    0x8289E5D0,\n    0x8289E618,\n    0x8289E618,\n]\n\n[[switch]]\nbase = 0x828A0750\nr = 10\ndefault = 0x828A0794\nlabels = [\n    0x828A0790,\n    0x828A0794,\n    0x828A077C,\n    0x828A0790,\n    0x828A0790,\n]\n\n[[switch]]\nbase = 0x828A27E0\nr = 11\ndefault = 0x828A2AE0\nlabels = [\n    0x828A2818,\n    0x828A2AE0,\n    0x828A2AE0,\n    0x828A2834,\n    0x828A288C,\n    0x828A28E0,\n    0x828A2A34,\n    0x828A2A8C,\n]\n\n[[switch]]\nbase = 0x828A28F0\nr = 11\ndefault = 0x828A2AE0\nlabels = [\n    0x828A291C,\n    0x828A2AE0,\n    0x828A2948,\n    0x828A299C,\n    0x828A29D0,\n]\n\n[[switch]]\nbase = 0x828A302C\nr = 11\ndefault = 0x828A3408\nlabels = [\n    0x828A3064,\n    0x828A3408,\n    0x828A3408,\n    0x828A307C,\n    0x828A3108,\n    0x828A315C,\n    0x828A32F0,\n    0x828A337C,\n]\n\n[[switch]]\nbase = 0x828A316C\nr = 11\ndefault = 0x828A3408\nlabels = [\n    0x828A31EC,\n    0x828A3408,\n    0x828A3198,\n    0x828A32A8,\n    0x828A3218,\n]\n\n[[switch]]\nbase = 0x828A7100\nr = 10\ndefault = 0x828A73FC\nlabels = [\n    0x828A7188,\n    0x828A7128,\n    0x828A7160,\n    0x828A7160,\n]\n\n[[switch]]\nbase = 0x828AD250\nr = 3\ndefault = 0x828AD3B0\nlabels = [\n    0x828AD288,\n    0x828AD2A4,\n    0x828AD2A4,\n    0x828AD2A4,\n    0x828AD2D0,\n    0x828AD314,\n    0x828AD3B0,\n    0x828AD370,\n]\n\n[[switch]]\nbase = 0x828B3A00\nr = 9\ndefault = 0x828B3A3C\nlabels = [\n    0x828B3A38,\n    0x828B3A3C,\n    0x828B3A3C,\n    0x828B3A38,\n    0x828B3A3C,\n    0x828B3A38,\n    0x828B3A38,\n    0x828B3A38,\n]\n\n[[switch]]\nbase = 0x828B9CD0\nr = 3\ndefault = 0x828B9D40\nlabels = [\n    0x828B9D40,\n    0x828B9D08,\n    0x828B9D10,\n    0x828B9D18,\n    0x828B9D20,\n    0x828B9D28,\n    0x828B9D30,\n    0x828B9D38,\n]\n\n[[switch]]\nbase = 0x828BFB20\nr = 3\ndefault = 0x828BFB58\nlabels = [\n    0x828BFB58,\n    0x828BFB48,\n    0x828BFB50,\n    0x828BFB58,\n]\n\n[[switch]]\nbase = 0x828C4B10\nr = 11\ndefault = 0x828C4BB0\nlabels = [\n    0x828C4B58,\n    0x828C4B38,\n    0x828C4B48,\n    0x828C4B48,\n]\n\n[[switch]]\nbase = 0x828C4BF8\nr = 11\ndefault = 0x0\nlabels = [\n    0x828C4C64,\n    0x828C4C20,\n    0x828C4C40,\n    0x828C4C30,\n]\n\n[[switch]]\nbase = 0x82908A68\nr = 11\ndefault = 0x82908C64\nlabels = [\n    0x82908ADC,\n    0x82908AEC,\n    0x82908AFC,\n    0x82908B2C,\n    0x82908B0C,\n    0x82908B40,\n    0x82908B4C,\n    0x82908B58,\n    0x82908B64,\n    0x82908B70,\n    0x82908B7C,\n    0x82908B88,\n    0x82908B94,\n    0x82908BA0,\n    0x82908BB4,\n    0x82908BC8,\n    0x82908BDC,\n    0x82908BF0,\n    0x82908C04,\n    0x82908C18,\n    0x82908C2C,\n    0x82908C40,\n    0x82908C54,\n]\n\n[[switch]]\nbase = 0x8290C928\nr = 11\ndefault = 0x8290C9C8\nlabels = [\n    0x8290C958,\n    0x8290C970,\n    0x8290C988,\n    0x8290C9A0,\n    0x8290C9C8,\n    0x8290C9C0,\n]\n\n[[switch]]\nbase = 0x82912540\nr = 10\ndefault = 0x829125F0\nlabels = [\n    0x82912570,\n    0x82912570,\n    0x82912578,\n    0x82912578,\n    0x82912580,\n    0x82912580,\n]\n\n[[switch]]\nbase = 0x829125A0\nr = 10\ndefault = 0x829125F0\nlabels = [\n    0x829125D0,\n    0x829125D0,\n    0x829125D8,\n    0x829125D8,\n    0x829125E4,\n    0x829125E4,\n]\n\n[[switch]]\nbase = 0x82912648\nr = 10\ndefault = 0x82912714\nlabels = [\n    0x82912678,\n    0x82912678,\n    0x82912680,\n    0x82912680,\n    0x82912698,\n    0x82912698,\n]\n\n[[switch]]\nbase = 0x829126C8\nr = 10\ndefault = 0x82912714\nlabels = [\n    0x829126F8,\n    0x829126F8,\n    0x82912700,\n    0x82912700,\n    0x82912708,\n    0x82912708,\n]\n\n[[switch]]\nbase = 0x82912770\nr = 10\ndefault = 0x82912848\nlabels = [\n    0x829127A0,\n    0x829127A0,\n    0x829127A8,\n    0x829127A8,\n    0x829127BC,\n    0x829127BC,\n]\n\n[[switch]]\nbase = 0x82912800\nr = 10\ndefault = 0x82912848\nlabels = [\n    0x82912830,\n    0x82912830,\n    0x82912838,\n    0x82912838,\n    0x82912840,\n    0x82912840,\n]\n\n[[switch]]\nbase = 0x82912A24\nr = 5\ndefault = 0x0\nlabels = [\n    0x82912A6C,\n    0x82912A6C,\n    0x82912A78,\n    0x82912A78,\n    0x82912A90,\n    0x82912A90,\n    0x82912AC0,\n    0x82912AC0,\n    0x82912B20,\n    0x82912B58,\n    0x82912BC0,\n    0x82912C08,\n]\n\n[[switch]]\nbase = 0x82914570\nr = 3\ndefault = 0x829145D8\nlabels = [\n    0x829145B8,\n    0x829145B8,\n    0x829145C0,\n    0x829145C0,\n    0x829145C8,\n    0x829145C8,\n    0x829145D0,\n    0x829145D0,\n    0x829145C8,\n    0x829145D0,\n    0x829145C8,\n    0x829145D0,\n]\n\n# ---- COMPUTED JUMPTABLE ----\n[[switch]]\nbase = 0x8253AEC8\nr = 10\ndefault = 0x8253B068\nlabels = [\n    0x8253AF80,\n    0x8253AFB0,\n    0x8253AF34,\n    0x8253AEF0,\n    0x8253B068,\n    0x8253AFDC,\n    0x8253B008,\n    0x8253B024,\n    0x8253B040,\n]\n\n[[switch]]\nbase = 0x82546200\nr = 10\ndefault = 0x825463F8\nlabels = [\n    0x825463F4,\n    0x825463F0,\n    0x825463EC,\n    0x82546228,\n    0x825463BC,\n    0x825463F0,\n    0x825463EC,\n    0x8254625C,\n    0x825462A0,\n    0x825462E0,\n    0x82546354,\n    0x82546374,\n    0x825463F4,\n    0x82546228,\n    0x825463BC,\n    0x825463F4,\n]\n\n[[switch]]\nbase = 0x82557084\nr = 31\ndefault = 0x825573E0\nlabels = [\n    0x825570B0,\n    0x825570AC,\n    0x825570B0,\n    0x82557274,\n    0x82557198,\n    0x82557198,\n    0x82557364,\n    0x82557388,\n    0x825573E0,\n    0x825573E0,\n    0x825570B0,\n    0x825573E0,\n    0x82557274,\n    0x825573E0,\n    0x825573CC,\n    0x825573D4,\n]\n\n[[switch]]\nbase = 0x825DFFB4\nr = 11\ndefault = 0x825E01B0\nlabels = [\n    0x825DFFDC,\n    0x825DFFF4,\n    0x825E000C,\n    0x825E0018,\n    0x825E01B0,\n    0x825E0018,\n    0x825E000C,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E0030,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E0048,\n    0x825E0080,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E00B4,\n    0x825E00B4,\n    0x825E012C,\n    0x825E0074,\n    0x825E0058,\n    0x825E01B0,\n    0x825E0144,\n    0x825E01B0,\n    0x825E01B0,\n    0x825E0154,\n]\n\n[[switch]]\nbase = 0x826E8324\nr = 10\ndefault = 0x826E85D0\nlabels = [\n    0x826E834C,\n    0x826E85D0,\n    0x826E834C,\n    0x826E85D0,\n    0x826E8478,\n    0x826E85D0,\n    0x826E834C,\n    0x826E834C,\n    0x826E8478,\n    0x826E8478,\n    0x826E85D0,\n    0x826E834C,\n]\n\n[[switch]]\nbase = 0x826E860C\nr = 11\ndefault = 0x826E83B8\nlabels = [\n    0x826E8634,\n    0x826E8664,\n    0x826E8694,\n    0x826E86C4,\n    0x826E86DC,\n    0x826E86D0,\n    0x826E86E8,\n    0x826E86F0,\n    0x826E86FC,\n]\n\n[[switch]]\nbase = 0x826EEF14\nr = 9\ndefault = 0x826EF1DC\nlabels = [\n    0x826EEF3C,\n    0x826EEF3C,\n    0x826EEF3C,\n    0x826EEF50,\n    0x826EEF50,\n    0x826EEF5C,\n    0x826EEF5C,\n    0x826EEF68,\n    0x826EEF68,\n    0x826EF1DC,\n    0x826EEF74,\n    0x826EEF90,\n    0x826EEF80,\n    0x826EEFA4,\n    0x826EEFA4,\n    0x826EEFA4,\n    0x826EEFA4,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF1DC,\n    0x826EF018,\n]\n\n[[switch]]\nbase = 0x826F264C\nr = 11\ndefault = 0x826F29B4\nlabels = [\n    0x826F2674,\n    0x826F26E8,\n    0x826F2764,\n    0x826F27E0,\n    0x826F2850,\n    0x826F28C4,\n    0x826F28E4,\n    0x826F2968,\n    0x826F292C,\n    0x826F29C0,\n    0x826F29B4,\n    0x826F2980,\n]\n\n[[switch]]\nbase = 0x8283011C\nr = 10\ndefault = 0x828302E0\nlabels = [\n    0x82830144,\n    0x82830178,\n    0x828301B0,\n    0x828302E8,\n    0x828301D8,\n    0x828301FC,\n    0x82830220,\n    0x8283024C,\n    0x82830270,\n]\n\n[[switch]]\nbase = 0x8283724C\nr = 11\ndefault = 0x828375BC\nlabels = [\n    0x828375D0,\n    0x8283751C,\n    0x82837534,\n    0x82837274,\n    0x828372C4,\n    0x828372D4,\n    0x828372E8,\n    0x82837310,\n    0x82837334,\n    0x82837348,\n    0x82837354,\n    0x82837384,\n    0x828373B4,\n    0x828373D8,\n    0x8283748C,\n    0x828374B0,\n    0x828373FC,\n    0x82837420,\n    0x82837444,\n    0x82837468,\n    0x828374D4,\n    0x828374F8,\n    0x82837540,\n    0x8283755C,\n    0x82837564,\n    0x82837588,\n]\n\n[[switch]]\nbase = 0x82860FDC\nr = 11\ndefault = 0x8286116C\nlabels = [\n    0x82861004,\n    0x8286116C,\n    0x82861010,\n    0x8286101C,\n    0x82861028,\n    0x82861034,\n    0x82861064,\n    0x82861040,\n    0x8286104C,\n    0x82861058,\n    0x82861070,\n    0x8286107C,\n    0x82861088,\n    0x82861094,\n    0x828610A0,\n    0x82861124,\n    0x82861130,\n    0x828610AC,\n    0x828610B8,\n    0x828610C4,\n    0x828610D0,\n    0x828610E8,\n    0x828610DC,\n    0x828610F4,\n    0x82861100,\n    0x8286110C,\n    0x82861118,\n    0x8286113C,\n    0x82861148,\n    0x82861154,\n    0x82861160,\n]\n\n[[switch]]\nbase = 0x828920B8\nr = 28\ndefault = 0x828920A8\nlabels = [\n    0x828922F4,\n    0x828922F4,\n    0x828920A8,\n    0x828922E8,\n    0x82892130,\n    0x828921A4,\n    0x828920E0,\n    0x82892100,\n    0x82892100,\n    0x828922F4,\n    0x828922F4,\n    0x828920E0,\n    0x828920E0,\n    0x82892100,\n    0x82892120,\n    0x82892274,\n    0x82892274,\n    0x82892120,\n    0x82892128,\n    0x828922E8,\n    0x828922E8,\n]\n\n[[switch]]\nbase = 0x82917564\nr = 11\ndefault = 0x82917740\nlabels = [\n    0x8291758C,\n    0x829175A4,\n    0x8291758C,\n    0x8291758C,\n    0x8291758C,\n    0x82917600,\n    0x82917600,\n    0x82917600,\n    0x82917600,\n    0x82917610,\n    0x82917710,\n    0x82917710,\n]\n\n[[switch]]\nbase = 0x8291A7F4\nr = 11\ndefault = 0x8291AA30\nlabels = [\n    0x8291A81C,\n    0x8291A828,\n    0x8291A834,\n    0x8291A840,\n    0x8291A84C,\n    0x8291A854,\n    0x8291A85C,\n    0x8291A868,\n    0x8291A874,\n    0x8291A880,\n    0x8291A88C,\n    0x8291A894,\n    0x8291A89C,\n    0x8291A8A4,\n    0x8291A8AC,\n    0x8291A8B4,\n    0x8291A8C0,\n    0x8291A8CC,\n    0x8291A8D8,\n    0x8291A8E4,\n    0x8291A8F0,\n    0x8291A8FC,\n    0x8291A908,\n    0x8291A914,\n    0x8291A920,\n    0x8291A92C,\n    0x8291A938,\n    0x8291A944,\n    0x8291A950,\n    0x8291A95C,\n    0x8291A968,\n    0x8291A974,\n    0x8291A980,\n    0x8291A98C,\n    0x8291A998,\n    0x8291A9A4,\n    0x8291A9B0,\n    0x8291A9BC,\n    0x8291A9C8,\n    0x8291A9D4,\n    0x8291A9E0,\n    0x8291A9EC,\n    0x8291A9F8,\n    0x8291AA04,\n    0x8291AA10,\n    0x8291AA18,\n    0x8291AA20,\n]\n\n[[switch]]\nbase = 0x82921C64\nr = 11\ndefault = 0x82921D9C\nlabels = [\n    0x82921C8C,\n    0x82921C98,\n    0x82921CB4,\n    0x82921CC4,\n    0x82921CD0,\n    0x82921CDC,\n    0x82921D00,\n    0x82921D24,\n    0x82921D48,\n    0x82921D6C,\n    0x82921D78,\n    0x82921D9C,\n    0x82921D84,\n    0x82921D90,\n]\n\n[[switch]]\nbase = 0x82927C10\nr = 11\ndefault = 0x82927D40\nlabels = [\n    0x82927D40,\n    0x82927C38,\n    0x82927C4C,\n    0x82927C4C,\n    0x82927C4C,\n    0x82927C54,\n    0x82927C54,\n    0x82927C54,\n    0x82927C54,\n    0x82927C5C,\n    0x82927D40,\n    0x82927D40,\n    0x82927BDC,\n    0x82927BFC,\n]\n\n[[switch]]\nbase = 0x82927ED4\nr = 11\ndefault = 0x829281A4\nlabels = [\n    0x82927EFC,\n    0x82927F04,\n    0x82927F10,\n    0x82927F1C,\n    0x82927F28,\n    0x82927F34,\n    0x82927F40,\n    0x82927F48,\n    0x82927F54,\n    0x82927F60,\n    0x82927F6C,\n    0x82927F78,\n    0x82927F84,\n    0x82927F90,\n    0x82927F9C,\n    0x82927FA8,\n    0x82927FB4,\n    0x82927FC0,\n    0x82927FCC,\n    0x82927FD8,\n    0x82927FE4,\n    0x82927FF0,\n    0x82927FFC,\n    0x82928008,\n    0x82928014,\n    0x82928020,\n    0x8292802C,\n    0x82928038,\n    0x82928044,\n    0x82928050,\n    0x8292805C,\n    0x82928068,\n    0x82928074,\n    0x82928080,\n    0x8292808C,\n    0x82928098,\n    0x829280A4,\n    0x829280B0,\n    0x829280B8,\n    0x829280C0,\n    0x829280CC,\n    0x829280D8,\n    0x829280E4,\n    0x829280F0,\n    0x829280FC,\n    0x82928108,\n    0x82928114,\n    0x82928120,\n    0x8292812C,\n    0x82928134,\n    0x8292813C,\n    0x82928144,\n    0x8292814C,\n    0x82928154,\n    0x8292815C,\n    0x82928164,\n    0x8292816C,\n    0x82928174,\n    0x8292817C,\n    0x82928184,\n    0x8292818C,\n    0x82928194,\n]\n\n[[switch]]\nbase = 0x82928A48\nr = 11\ndefault = 0x82928BA8\nlabels = [\n    0x82928A80,\n    0x82928A70,\n    0x82928A90,\n    0x82928AA0,\n    0x82928AB0,\n    0x82928AC0,\n    0x82928AD0,\n    0x82928AE0,\n    0x82928AF0,\n    0x82928B00,\n    0x82928B10,\n    0x82928B20,\n    0x82928BA8,\n    0x82928B30,\n    0x82928B40,\n    0x82928B50,\n    0x82928B60,\n    0x82928B40,\n    0x82928B6C,\n    0x82928B60,\n    0x82928B40,\n    0x82928B50,\n    0x82928B78,\n    0x82928B88,\n    0x82928B98,\n]\n\n[[switch]]\nbase = 0x829448A4\nr = 29\ndefault = 0x8294476C\nlabels = [\n    0x8294496C,\n    0x82944980,\n    0x82944988,\n    0x82944990,\n    0x82944998,\n    0x829449A0,\n    0x829449A8,\n    0x829449B0,\n    0x829449C0,\n    0x8294476C,\n    0x829449CC,\n]\n\n[[switch]]\nbase = 0x82945ED0\nr = 11\ndefault = 0x82945C28\nlabels = [\n    0x82945F20,\n    0x82945F10,\n    0x82945F30,\n    0x82945F28,\n    0x82945F18,\n    0x82945F08,\n    0x82945F00,\n    0x82945EF8,\n    0x82945F38,\n]\n\n[[switch]]\nbase = 0x82949D04\nr = 11\ndefault = 0x82949EB0\nlabels = [\n    0x82949EB0,\n    0x82949D3C,\n    0x82949D50,\n    0x82949D50,\n    0x82949D50,\n    0x82949D58,\n    0x82949D58,\n    0x82949D58,\n    0x82949D58,\n    0x82949D60,\n    0x82949EB0,\n    0x82949EB0,\n    0x82949C48,\n    0x82949D34,\n]\n\n[[switch]]\nbase = 0x8294A1B8\nr = 11\ndefault = 0x8294A32C\nlabels = [\n    0x8294A1E0,\n    0x8294A1F0,\n    0x8294A200,\n    0x8294A20C,\n    0x8294A218,\n    0x8294A224,\n    0x8294A230,\n    0x8294A23C,\n    0x8294A248,\n    0x8294A254,\n    0x8294A260,\n    0x8294A26C,\n    0x8294A27C,\n    0x8294A288,\n    0x8294A290,\n    0x8294A29C,\n    0x8294A2A8,\n    0x8294A2B4,\n    0x8294A2C8,\n    0x8294A2F0,\n    0x8294A304,\n    0x8294A2DC,\n]\n\n[[switch]]\nbase = 0x829549D0\nr = 30\ndefault = 0x82954BA8\nlabels = [\n    0x82954BA0,\n    0x829549F8,\n    0x82954A00,\n    0x82954A08,\n    0x82954A10,\n    0x82954A18,\n    0x82954A20,\n    0x82954A28,\n    0x82954A30,\n    0x82954A38,\n    0x82954A40,\n    0x82954A48,\n    0x82954A50,\n    0x82954A58,\n    0x82954A60,\n    0x82954BA8,\n    0x82954BA8,\n    0x82954A68,\n    0x82954A68,\n]\n\n[[switch]]\nbase = 0x82970CD4\nr = 30\ndefault = 0x82970DF0\nlabels = [\n    0x82971028,\n    0x82970CFC,\n    0x82970DAC,\n    0x82970DB4,\n    0x82970DBC,\n    0x82970DC4,\n    0x82970DCC,\n    0x82970DD4,\n    0x82970DDC,\n    0x82970DE4,\n    0x82970E00,\n    0x82970E08,\n    0x82970E10,\n    0x82970E18,\n    0x82970E20,\n    0x82970DF0,\n    0x82970DF0,\n    0x82970E30,\n    0x82970E28,\n]\n\n[[switch]]\nbase = 0x8298EE5C\nr = 11\ndefault = 0x8298EF8C\nlabels = [\n    0x8298EE84,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE90,\n    0x8298EE9C,\n    0x8298EEA8,\n    0x8298EE9C,\n    0x8298EEB4,\n    0x8298EEC0,\n    0x8298EECC,\n    0x8298EED8,\n    0x8298EEE4,\n    0x8298EEF0,\n    0x8298EEFC,\n    0x8298EF08,\n    0x8298EF14,\n    0x8298EF20,\n    0x8298EF2C,\n    0x8298EF38,\n    0x8298EF44,\n    0x8298EF50,\n    0x8298EF5C,\n    0x8298EF68,\n    0x8298EF74,\n    0x8298EF80,\n]\n\n[[switch]]\nbase = 0x8299389C\nr = 11\ndefault = 0x82993BB0\nlabels = [\n    0x829938C4,\n    0x82993924,\n    0x8299392C,\n    0x82993934,\n    0x8299393C,\n    0x82993960,\n    0x8299398C,\n    0x82993994,\n    0x8299399C,\n    0x829939A8,\n    0x829939B4,\n    0x829939C0,\n    0x829939CC,\n    0x829939D4,\n    0x829939DC,\n    0x829939E8,\n]\n\n[[switch]]\nbase = 0x829B5044\nr = 30\ndefault = 0x829B5288\nlabels = [\n    0x829B52C0,\n    0x829B506C,\n    0x829B5244,\n    0x829B524C,\n    0x829B5254,\n    0x829B525C,\n    0x829B5264,\n    0x829B526C,\n    0x829B5274,\n    0x829B527C,\n    0x829B5298,\n    0x829B52A0,\n    0x829B52A8,\n    0x829B52B0,\n    0x829B52B8,\n]\n\n[[switch]]\nbase = 0x829C3A20\nr = 11\ndefault = 0x829C3A6C\nlabels = [\n    0x829C3A48,\n    0x829C3B00,\n    0x829C3A64,\n    0x829C3B18,\n    0x829C3B24,\n    0x829C3B3C,\n    0x829C3AB8,\n    0x829C3AD0,\n    0x829C3B54,\n    0x829C3AE8,\n    0x829C3A6C,\n    0x829C3B8C,\n    0x829C3A6C,\n    0x829C3B6C,\n]\n\n[[switch]]\nbase = 0x82A648BC\nr = 11\ndefault = 0x82A64B30\nlabels = [\n    0x82A648E4,\n    0x82A64B30,\n    0x82A64A5C,\n    0x82A64AA0,\n    0x82A649AC,\n    0x82A649AC,\n    0x82A649E4,\n    0x82A648FC,\n    0x82A648FC,\n    0x82A64A0C,\n    0x82A649F4,\n    0x82A649F4,\n    0x82A64A20,\n]\n\n[[switch]]\nbase = 0x82A68914\nr = 11\ndefault = 0x82A68C74\nlabels = [\n    0x82A68A70,\n    0x82A68ABC,\n    0x82A68AD0,\n    0x82A68AAC,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A6893C,\n    0x82A68960,\n    0x82A68990,\n    0x82A689A8,\n    0x82A689D0,\n    0x82A68A00,\n    0x82A68A24,\n    0x82A68A2C,\n    0x82A68A34,\n    0x82A68B00,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68A54,\n    0x82A68A70,\n    0x82A68A84,\n    0x82A68A98,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68A70,\n    0x82A68ABC,\n    0x82A68AD0,\n    0x82A68AAC,\n    0x82A68AE4,\n    0x82A68A3C,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68C74,\n    0x82A68A44,\n    0x82A68A4C,\n]\n\n[[switch]]\nbase = 0x82A976DC\nr = 4\ndefault = 0x82A9784C\nlabels = [\n    0x82A977FC,\n    0x82A977FC,\n    0x82A97704,\n    0x82A97718,\n    0x82A97730,\n    0x82A97750,\n    0x82A977A4,\n    0x82A977C0,\n    0x82A97704,\n    0x82A97704,\n    0x82A97774,\n    0x82A977A4,\n    0x82A977A4,\n    0x82A9784C,\n    0x82A977A4,\n    0x82A97790,\n    0x82A977CC,\n    0x82A977D8,\n    0x82A9781C,\n    0x82A97824,\n    0x82A97824,\n    0x82A9784C,\n    0x82A97804,\n    0x82A97804,\n    0x82A9779C,\n    0x82A977F4,\n    0x82A97814,\n    0x82A9779C,\n    0x82A977F4,\n    0x82A97814,\n    0x82A9779C,\n    0x82A977F4,\n    0x82A97814,\n    0x82A977FC,\n    0x82A9781C,\n    0x82A97824,\n    0x82A977FC,\n    0x82A9781C,\n    0x82A97824,\n    0x82A977A4,\n    0x82A977A4,\n    0x82A9779C,\n    0x82A977F4,\n    0x82A97704,\n    0x82A977A4,\n    0x82A977A4,\n    0x82A9779C,\n    0x82A9779C,\n    0x82A977F4,\n    0x82A97824,\n    0x82A977A4,\n    0x82A9781C,\n    0x82A97824,\n    0x82A97824,\n    0x82A977C0,\n    0x82A977CC,\n    0x82A977D8,\n    0x82A9782C,\n    0x82A9781C,\n    0x82A9781C,\n    0x82A9781C,\n    0x82A9781C,\n]\n\n[[switch]]\nbase = 0x82A97878\nr = 10\ndefault = 0x82A979B8\nlabels = [\n    0x82A978A0,\n    0x82A978B4,\n    0x82A978CC,\n    0x82A978EC,\n    0x82A97940,\n    0x82A9795C,\n    0x82A978A0,\n    0x82A978A0,\n    0x82A97910,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A97940,\n    0x82A9792C,\n    0x82A97968,\n    0x82A97974,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A97938,\n    0x82A97990,\n    0x82A979A0,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A97938,\n    0x82A97990,\n    0x82A979A0,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A979B8,\n    0x82A97998,\n    0x82A979A8,\n    0x82A979B0,\n]\n\n# ---- OFFSETED JUMPTABLE ----\n[[switch]]\nbase = 0x8253B77C\nr = 10\ndefault = 0x0\nlabels = [\n    0x8253B7A4,\n    0x8253B7F4,\n    0x8253B7CC,\n    0x8253B808,\n    0x8253B820,\n    0x8253B834,\n    0x8253B83C,\n    0x8253B844,\n    0x8253B844,\n]\n\n[[switch]]\nbase = 0x825478B0\nr = 10\ndefault = 0x8254792C\nlabels = [\n    0x8254790C,\n    0x8254790C,\n    0x8254792C,\n    0x8254792C,\n    0x825478D8,\n    0x825478D8,\n    0x825478F8,\n    0x8254792C,\n    0x825478F8,\n    0x8254792C,\n    0x8254790C,\n    0x8254790C,\n]\n\n[[switch]]\nbase = 0x825D8B7C\nr = 3\ndefault = 0x825D8C14\nlabels = [\n    0x825D8C80,\n    0x825D8BC8,\n    0x825D8C50,\n    0x825D8BA4,\n    0x825D8BB8,\n    0x825D8BF4,\n    0x825D8C38,\n    0x825D8C50,\n    0x825D8C68,\n]\n\n[[switch]]\nbase = 0x826EA534\nr = 11\ndefault = 0x826EA600\nlabels = [\n    0x826EA55C,\n    0x826EA55C,\n    0x826EA568,\n    0x826EA568,\n    0x826EA590,\n    0x826EA574,\n    0x826EA580,\n    0x826EA580,\n]\n\n[[switch]]\nbase = 0x82705AD8\nr = 11\ndefault = 0x82705BB8\nlabels = [\n    0x82705B00,\n    0x82705B40,\n    0x82705B90,\n    0x82705B54,\n    0x82705B2C,\n    0x82705B14,\n    0x82705BA4,\n    0x82705B7C,\n    0x82705B68,\n]\n\n[[switch]]\nbase = 0x82833094\nr = 11\ndefault = 0x0\nlabels = [\n    0x828330BC,\n    0x828330D4,\n    0x828330EC,\n    0x828330F4,\n    0x828330FC,\n    0x82833104,\n    0x8283310C,\n    0x82833114,\n    0x8283311C,\n]\n\n[[switch]]\nbase = 0x8292BE18\nr = 11\ndefault = 0x8292BE80\nlabels = [\n    0x8292BE40,\n    0x8292BE48,\n    0x8292BE50,\n    0x8292BE5C,\n    0x8292BE64,\n    0x8292BE48,\n    0x8292BE50,\n    0x8292BE5C,\n    0x8292BE64,\n    0x8292BE80,\n    0x8292BE6C,\n    0x8292BE74,\n    0x8292BE7C,\n]\n\n[[switch]]\nbase = 0x8293F258\nr = 11\ndefault = 0x8293F344\nlabels = [\n    0x8293F280,\n    0x8293F294,\n    0x8293F280,\n    0x8293F280,\n    0x8293F280,\n    0x8293F2CC,\n    0x8293F2CC,\n    0x8293F2CC,\n    0x8293F2CC,\n    0x8293F2DC,\n    0x8293F30C,\n]\n\n[[switch]]\nbase = 0x82944EA4\nr = 9\ndefault = 0x82944F60\nlabels = [\n    0x82944ECC,\n    0x82944F00,\n    0x82944EE4,\n    0x82944EE4,\n    0x82944EE4,\n    0x82944ECC,\n    0x82944EEC,\n    0x82944EEC,\n    0x82944ECC,\n    0x82944ECC,\n    0x82944F28,\n    0x82944EE4,\n    0x82944EE4,\n    0x82944F30,\n    0x82944F40,\n]\n\n[[switch]]\nbase = 0x8294C38C\nr = 10\ndefault = 0x8294C404\nlabels = [\n    0x8294C3F0,\n    0x8294C3E4,\n    0x8294C3B4,\n    0x8294C3B4,\n    0x8294C3B4,\n    0x8294C3C0,\n    0x8294C3D8,\n    0x8294C3FC,\n    0x8294C3CC,\n]\n\n[[switch]]\nbase = 0x829667EC\nr = 10\ndefault = 0x829668B4\nlabels = [\n    0x82966814,\n    0x8296681C,\n    0x8296681C,\n    0x8296681C,\n    0x8296681C,\n    0x8296681C,\n    0x8296681C,\n    0x8296681C,\n    0x8296681C,\n    0x82966824,\n    0x82966824,\n    0x82966824,\n    0x82966824,\n    0x8296681C,\n    0x8296682C,\n    0x82966834,\n    0x8296683C,\n    0x82966844,\n    0x8296684C,\n    0x82966854,\n    0x8296685C,\n    0x82966888,\n    0x82966864,\n    0x82966874,\n    0x82966890,\n    0x82966898,\n    0x829668A0,\n    0x829668A8,\n    0x829668B0,\n]\n\n[[switch]]\nbase = 0x8298D264\nr = 11\ndefault = 0x8298D29C\nlabels = [\n    0x8298D28C,\n    0x8298D2A4,\n    0x8298D2C4,\n    0x8298D2E8,\n    0x8298D28C,\n    0x8298D2A4,\n    0x8298D2C4,\n    0x8298D2E8,\n    0x8298D308,\n    0x8298D318,\n    0x8298D328,\n    0x8298D338,\n]\n\n[[switch]]\nbase = 0x8298D6FC\nr = 11\ndefault = 0x8298D750\nlabels = [\n    0x8298D724,\n    0x8298D72C,\n    0x8298D72C,\n    0x8298D72C,\n    0x8298D72C,\n    0x8298D734,\n    0x8298D734,\n    0x8298D734,\n    0x8298D734,\n    0x8298D73C,\n    0x8298D73C,\n    0x8298D73C,\n    0x8298D73C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D744,\n    0x8298D744,\n    0x8298D744,\n    0x8298D744,\n    0x8298D744,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D74C,\n    0x8298D744,\n]\n\n[[switch]]\nbase = 0x8298E40C\nr = 11\ndefault = 0x8298E4A0\nlabels = [\n    0x8298E434,\n    0x8298E440,\n    0x8298E45C,\n    0x8298E45C,\n    0x8298E45C,\n    0x8298E468,\n    0x8298E468,\n    0x8298E468,\n    0x8298E468,\n    0x8298E474,\n    0x8298E47C,\n    0x8298E4A0,\n    0x8298E488,\n    0x8298E494,\n]\n\n[[switch]]\nbase = 0x829C7278\nr = 9\ndefault = 0x829C7330\nlabels = [\n    0x829C72A0,\n    0x829C72A8,\n    0x829C72B0,\n    0x829C72B8,\n    0x829C72C0,\n    0x829C72C8,\n    0x829C72D4,\n    0x829C72DC,\n    0x829C72E4,\n    0x829C72EC,\n    0x829C72F4,\n    0x829C72FC,\n    0x829C7304,\n    0x829C730C,\n    0x829C7314,\n    0x829C731C,\n    0x829C7324,\n    0x829C732C,\n]\n\n[[switch]]\nbase = 0x829CDB24\nr = 11\ndefault = 0x829CDB68\nlabels = [\n    0x829CDB60,\n    0x829CDB60,\n    0x829CDB4C,\n    0x829CDB4C,\n    0x829CDB4C,\n    0x829CDB58,\n    0x829CDB58,\n    0x829CDB60,\n    0x829CDB4C,\n    0x829CDB60,\n    0x829CDB4C,\n    0x829CDB68,\n    0x829CDB4C,\n    0x829CDB4C,\n    0x829CDB68,\n    0x829CDB68,\n    0x829CDB68,\n    0x829CDB68,\n    0x829CDB58,\n    0x829CDB4C,\n    0x829CDB50,\n    0x829CDB50,\n    0x829CDB50,\n]\n\n[[switch]]\nbase = 0x829CFD40\nr = 11\ndefault = 0x829CFDBC\nlabels = [\n    0x829CFD68,\n    0x829CFD68,\n    0x829CFD88,\n    0x829CFD88,\n    0x829CFD88,\n    0x829CFD88,\n    0x829CFD90,\n    0x829CFD90,\n    0x829CFD90,\n    0x829CFDBC,\n    0x829CFD90,\n]\n\n[[switch]]\nbase = 0x829CFE78\nr = 11\ndefault = 0x829CFF2C\nlabels = [\n    0x829CFEA0,\n    0x829CFEAC,\n    0x829CFEB8,\n    0x829CFEC4,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFED0,\n    0x829CFEDC,\n    0x829CFEE8,\n    0x829CFEF4,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF2C,\n    0x829CFF00,\n    0x829CFF0C,\n    0x829CFF18,\n    0x829CFF24,\n]\n\n[[switch]]\nbase = 0x829D9E9C\nr = 4\ndefault = 0x829D9EE0\nlabels = [\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9EDC,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9EDC,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9EDC,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9ECC,\n    0x829D9ECC,\n    0x829D9ECC,\n    0x829D9ECC,\n    0x829D9ECC,\n    0x829D9ECC,\n    0x829D9ECC,\n    0x829D9ED4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n    0x829D9EC4,\n]\n\n[[switch]]\nbase = 0x829DA63C\nr = 11\ndefault = 0x0\nlabels = [\n    0x829DA680,\n    0x829DA688,\n    0x829DA690,\n    0x829DA690,\n    0x829DA694,\n    0x829DA690,\n    0x829DA690,\n    0x829DA694,\n    0x829DA694,\n    0x829DA694,\n    0x829DA678,\n    0x829DA694,\n    0x829DA670,\n    0x829DA670,\n    0x829DA694,\n    0x829DA670,\n    0x829DA670,\n    0x829DA694,\n    0x829DA694,\n    0x829DA690,\n    0x829DA694,\n    0x829DA694,\n    0x829DA694,\n    0x829DA690,\n    0x829DA694,\n    0x829DA694,\n    0x829DA694,\n    0x829DA678,\n    0x829DA690,\n    0x829DA694,\n    0x829DA688,\n    0x829DA694,\n    0x829DA694,\n    0x829DA694,\n    0x829DA694,\n    0x829DA690,\n    0x829DA664,\n]\n\n[[switch]]\nbase = 0x82A4B87C\nr = 3\ndefault = 0x82A4B8E0\nlabels = [\n    0x82A4B8A4,\n    0x82A4B8AC,\n    0x82A4B8B4,\n    0x82A4B8BC,\n    0x82A4B8C4,\n    0x82A4B8CC,\n    0x82A4B8D4,\n    0x82A4B8DC,\n]\n\n[[switch]]\nbase = 0x82A51660\nr = 11\ndefault = 0x82A516C0\nlabels = [\n    0x82A516B8,\n    0x82A516B8,\n    0x82A516B8,\n    0x82A516D8,\n    0x82A516D8,\n    0x82A516D8,\n    0x82A516B8,\n    0x82A516D8,\n    0x82A516B8,\n    0x82A516B8,\n    0x82A516B8,\n    0x82A516C0,\n    0x82A516C0,\n    0x82A516D8,\n    0x82A51688,\n    0x82A516B0,\n    0x82A516B0,\n    0x82A516B0,\n    0x82A516B0,\n    0x82A516B0,\n    0x82A516B8,\n    0x82A516B8,\n    0x82A516B8,\n    0x82A516B0,\n    0x82A516B0,\n    0x82A516C0,\n    0x82A51694,\n    0x82A51694,\n]\n\n[[switch]]\nbase = 0x82A51954\nr = 9\ndefault = 0x82A51A14\nlabels = [\n    0x82A519D0,\n    0x82A519D0,\n    0x82A519D0,\n    0x82A519FC,\n    0x82A5197C,\n    0x82A5197C,\n    0x82A5197C,\n    0x82A519B0,\n    0x82A5197C,\n    0x82A519B0,\n    0x82A519C0,\n    0x82A51A14,\n    0x82A51A14,\n    0x82A519FC,\n    0x82A519FC,\n    0x82A5197C,\n    0x82A5197C,\n    0x82A5197C,\n    0x82A5197C,\n    0x82A5197C,\n    0x82A519A8,\n    0x82A519FC,\n    0x82A519A8,\n    0x82A519FC,\n    0x82A519FC,\n]\n\n[[switch]]\nbase = 0x82A65798\nr = 10\ndefault = 0x82A658A8\nlabels = [\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A658A8,\n    0x82A657DC,\n    0x82A657DC,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657F4,\n    0x82A658A0,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A658A8,\n    0x82A657DC,\n    0x82A657DC,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A6580C,\n    0x82A6580C,\n    0x82A6580C,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A657C0,\n    0x82A6580C,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A65824,\n    0x82A65824,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A8,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A0,\n    0x82A658A8,\n    0x82A65834,\n    0x82A65860,\n    0x82A658A0,\n    0x82A65860,\n    0x82A65860,\n    0x82A658A0,\n    0x82A658A0,\n]\n\n[[switch]]\nbase = 0x82A8362C\nr = 4\ndefault = 0x82A83684\nlabels = [\n    0x82A836AC,\n    0x82A8366C,\n    0x82A83674,\n    0x82A8367C,\n    0x82A836A8,\n    0x82A83654,\n    0x82A8365C,\n    0x82A83664,\n]\n\n[[switch]]\nbase = 0x82A975A8\nr = 3\ndefault = 0x82A97600\nlabels = [\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975D0,\n    0x82A975D8,\n    0x82A975D8,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975D0,\n    0x82A975D0,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A97600,\n    0x82A975E0,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975F0,\n    0x82A975F0,\n    0x82A97600,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975F0,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975F0,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975D0,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975D8,\n    0x82A975D8,\n    0x82A975E0,\n    0x82A975F0,\n    0x82A975E0,\n    0x82A975E8,\n    0x82A975F0,\n    0x82A975F0,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975E0,\n    0x82A975F8,\n    0x82A975E8,\n    0x82A975E8,\n    0x82A975E8,\n    0x82A975E8,\n]\n\n[[switch]]\nbase = 0x82A97614\nr = 11\ndefault = 0x82A97664\nlabels = [\n    0x82A9763C,\n    0x82A97644,\n    0x82A97644,\n    0x82A97644,\n    0x82A9764C,\n    0x82A9764C,\n    0x82A9763C,\n    0x82A9763C,\n    0x82A97644,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A9764C,\n    0x82A97644,\n    0x82A9764C,\n    0x82A9764C,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A97644,\n    0x82A9764C,\n    0x82A97654,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A97644,\n    0x82A9764C,\n    0x82A97654,\n    0x82A97664,\n    0x82A97664,\n    0x82A97664,\n    0x82A9764C,\n    0x82A97654,\n    0x82A9765C,\n]\n\n[[switch]]\nbase = 0x82A9767C\nr = 11\ndefault = 0x82A976C4\nlabels = [\n    0x82A976BC,\n    0x82A976A4,\n    0x82A976AC,\n    0x82A976AC,\n    0x82A976A4,\n    0x82A976A4,\n    0x82A976BC,\n    0x82A976BC,\n    0x82A976B4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976A4,\n    0x82A976A4,\n    0x82A976AC,\n    0x82A976AC,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976BC,\n    0x82A976B4,\n    0x82A976A4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976BC,\n    0x82A976B4,\n    0x82A976A4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976C4,\n    0x82A976BC,\n    0x82A976B4,\n    0x82A976A4,\n]\n\n[[switch]]\nbase = 0x82AAE258\nr = 11\ndefault = 0x82AAE310\nlabels = [\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE280,\n    0x82AAE2F0,\n    0x82AAE290,\n    0x82AAE290,\n    0x82AAE2C8,\n    0x82AAE37C,\n    0x82AAE280,\n    0x82AAE2DC,\n    0x82AAE2CC,\n    0x82AAE300,\n    0x82AAE2BC,\n]\n\n[[switch]]\nbase = 0x8254BA8C\nr = 30\ndefault = 0x8254C0D4\nlabels = [\n    0x8254BAB4,\n    0x8254BAD4,\n    0x8254BB00,\n    0x8254BB18,\n    0x8254BB7C,\n    0x8254BB98,\n    0x8254BBD8,\n    0x8254BC7C,\n    0x8254BD28,\n    0x8254BDA8,\n    0x8254BE50,\n    0x8254BEAC,\n    0x8254BF38,\n    0x8254BF98,\n    0x8254BFEC,\n    0x8254C040,\n    0x8254C0A4,\n    0x8254BC6C,\n    0x8254BC7C,\n]\n\n[[switch]]\nbase = 0x825CF128\nr = 8\ndefault = 0x825CF5E0\nlabels = [\n    0x825CF150,\n    0x825CF198,\n    0x825CF1F0,\n    0x825CF248,\n    0x825CF28C,\n    0x825CF2D8,\n    0x825CF324,\n    0x825CF388,\n    0x825CF3D4,\n    0x825CF428,\n    0x825CF474,\n    0x825CF4C0,\n    0x825CF50C,\n    0x825CF550,\n    0x825CF594,\n]\n\n[[switch]]\nbase = 0x825DD358\nr = 11\ndefault = 0x825DD2F0\nlabels = [\n    0x825DD380,\n    0x825DD39C,\n    0x825DD3A8,\n    0x825DD3CC,\n    0x825DD3E8,\n    0x825DD400,\n    0x825DD45C,\n    0x825DD4C4,\n    0x825DD4E0,\n    0x825DD504,\n    0x825DD554,\n    0x825DD57C,\n    0x825DD5EC,\n    0x825DD674,\n    0x825DD6F0,\n    0x825DD76C,\n    0x825DD7E8,\n    0x825DD838,\n    0x825DD8AC,\n    0x825DD8EC,\n    0x825DD928,\n    0x825DD940,\n    0x825DDA0C,\n    0x825DDA80,\n    0x825DDB80,\n    0x825DDBD0,\n    0x825DDBD0,\n    0x825DDF44,\n    0x825DDC30,\n    0x825DDCF0,\n    0x825DDDA8,\n    0x825DDDF0,\n    0x825DDDF0,\n    0x825DDE78,\n    0x825DDE88,\n]\n\n[[switch]]\nbase = 0x826EBB08\nr = 10\ndefault = 0x826EBAE0\nlabels = [\n    0x826EBB4C,\n    0x826EB814,\n    0x826EBBE4,\n    0x826EBB30,\n    0x826EBB30,\n    0x826EBD54,\n    0x826EBD54,\n    0x826EBAE0,\n    0x826EBB30,\n    0x826EBB30,\n    0x826EBAE0,\n    0x826EBAE0,\n    0x826EBAE0,\n    0x826EBAE0,\n    0x826EBAE0,\n    0x826EBD70,\n]\n\n[[switch]]\nbase = 0x826EFA74\nr = 11\ndefault = 0x826F038C\nlabels = [\n    0x826EFC68,\n    0x826EFA9C,\n    0x826EFABC,\n    0x826EFB0C,\n    0x826EFB58,\n    0x826EFB60,\n    0x826EFB98,\n    0x826EFCB8,\n]\n\n[[switch]]\nbase = 0x826EFCC4\nr = 11\ndefault = 0x826F01B8\nlabels = [\n    0x826EFEC8,\n    0x826F01B8,\n    0x826EFCEC,\n    0x826F01B8,\n    0x826EFEC8,\n    0x826F01B8,\n    0x826EFEC8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826EFDD4,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F0008,\n    0x826F01B8,\n    0x826EFD58,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826EFED4,\n    0x826F01B8,\n    0x826EFCFC,\n    0x826EFFF8,\n    0x826EFED4,\n    0x826EFED4,\n    0x826EFED4,\n    0x826F01B8,\n    0x826EFFF8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F01B8,\n    0x826EFE8C,\n    0x826F0040,\n    0x826F0004,\n    0x826F01B8,\n    0x826F01B8,\n    0x826EFDE4,\n    0x826F01B8,\n    0x826EFFFC,\n    0x826F01B8,\n    0x826F01B8,\n    0x826F0010,\n]\n\n[[switch]]\nbase = 0x826F1A10\nr = 11\ndefault = 0x826F23C4\nlabels = [\n    0x826F1BEC,\n    0x826F1A38,\n    0x826F1A58,\n    0x826F1AA8,\n    0x826F1AE4,\n    0x826F1AEC,\n    0x826F1B24,\n    0x826F1C08,\n]\n\n[[switch]]\nbase = 0x826F1C14\nr = 11\ndefault = 0x826F2124\nlabels = [\n    0x826F1E44,\n    0x826F2124,\n    0x826F1C3C,\n    0x826F2124,\n    0x826F1E44,\n    0x826F2124,\n    0x826F1E44,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F1D30,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F1F88,\n    0x826F2124,\n    0x826F1CB0,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F1E50,\n    0x826F2124,\n    0x826F1C4C,\n    0x826F1F78,\n    0x826F1E50,\n    0x826F1E50,\n    0x826F1E50,\n    0x826F2124,\n    0x826F1F78,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F2124,\n    0x826F1E08,\n    0x826F1FBC,\n    0x826F1F84,\n    0x826F2124,\n    0x826F2124,\n    0x826F1D40,\n    0x826F2124,\n    0x826F1F7C,\n    0x826F2124,\n    0x826F2124,\n    0x826F1F90,\n]\n\n[[switch]]\nbase = 0x826F37FC\nr = 11\ndefault = 0x826F43C4\nlabels = [\n    0x826F3824,\n    0x826F3CC0,\n    0x826F3FEC,\n    0x826F3FEC,\n    0x826F3FEC,\n    0x826F43C4,\n    0x826F3B60,\n    0x826F43C4,\n    0x826F43C4,\n    0x826F43C4,\n    0x826F43C4,\n    0x826F3F98,\n    0x826F3CC0,\n    0x826F3CBC,\n    0x826F43C4,\n    0x826F43C4,\n    0x826F3834,\n    0x826F43C4,\n    0x826F3CC0,\n    0x826F43C4,\n    0x826F43C4,\n    0x826F3B64,\n    0x826F43C4,\n    0x826F43C4,\n    0x826F3844,\n]\n\n[[switch]]\nbase = 0x826F6500\nr = 11\ndefault = 0x826F6E18\nlabels = [\n    0x826F66F4,\n    0x826F6528,\n    0x826F6548,\n    0x826F6598,\n    0x826F65E4,\n    0x826F65EC,\n    0x826F6624,\n    0x826F6744,\n]\n\n[[switch]]\nbase = 0x826F6750\nr = 11\ndefault = 0x826F6C44\nlabels = [\n    0x826F6954,\n    0x826F6C44,\n    0x826F6778,\n    0x826F6C44,\n    0x826F6954,\n    0x826F6C44,\n    0x826F6954,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6860,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6A94,\n    0x826F6C44,\n    0x826F67E4,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6960,\n    0x826F6C44,\n    0x826F6788,\n    0x826F6A84,\n    0x826F6960,\n    0x826F6960,\n    0x826F6960,\n    0x826F6C44,\n    0x826F6A84,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6918,\n    0x826F6ACC,\n    0x826F6A90,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6870,\n    0x826F6C44,\n    0x826F6A88,\n    0x826F6C44,\n    0x826F6C44,\n    0x826F6A9C,\n]\n\n[[switch]]\nbase = 0x82722608\nr = 10\ndefault = 0x82722930\nlabels = [\n    0x82722630,\n    0x827226BC,\n    0x82722950,\n    0x827229A0,\n    0x827229F8,\n    0x82722A4C,\n    0x82722AAC,\n    0x82722734,\n    0x827227B4,\n    0x82722804,\n    0x8272285C,\n    0x827228B4,\n    0x82722AD4,\n    0x82722ADC,\n]\n\n[[switch]]\nbase = 0x8272C1F8\nr = 10\ndefault = 0x8272C8BC\nlabels = [\n    0x8272C244,\n    0x8272C33C,\n    0x8272C390,\n    0x8272C4D0,\n    0x8272C594,\n    0x8272C774,\n    0x8272C818,\n    0x8272C990,\n    0x8272C9C0,\n    0x8272C928,\n]\n\n[[switch]]\nbase = 0x8272E1F0\nr = 9\ndefault = 0x8272E714\nlabels = [\n    0x8272E218,\n    0x8272E2DC,\n    0x8272E3A8,\n    0x8272E414,\n    0x8272E4A8,\n    0x8272E504,\n    0x8272E634,\n    0x8272E77C,\n    0x8272E7BC,\n    0x8272E774,\n]\n\n[[switch]]\nbase = 0x82839C70\nr = 11\ndefault = 0x8283A108\nlabels = [\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x82839C98,\n    0x82839CBC,\n    0x82839D34,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x82839DAC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x82839E6C,\n    0x82839EE4,\n    0x82839F5C,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A108,\n    0x8283A0EC,\n    0x82839FFC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A074,\n    0x8283A0B0,\n    0x8283A108,\n    0x8283A0EC,\n    0x8283A0EC,\n    0x8283A0EC,\n]\n\n[[switch]]\nbase = 0x8283A300\nr = 11\ndefault = 0x8283AAF4\nlabels = [\n    0x8283A328,\n    0x8283A348,\n    0x8283A784,\n    0x8283A368,\n    0x8283A380,\n    0x8283A3D8,\n    0x8283A448,\n    0x8283A4B4,\n    0x8283A4F0,\n    0x8283A510,\n    0x8283A5E0,\n    0x8283A5FC,\n    0x8283A674,\n    0x8283A690,\n    0x8283A6AC,\n    0x8283A718,\n    0x8283A7A8,\n    0x8283A80C,\n    0x8283A830,\n    0x8283A850,\n    0x8283A864,\n    0x8283AA04,\n    0x8283A870,\n    0x8283A8A4,\n    0x8283A8E0,\n    0x8283A91C,\n    0x8283A958,\n    0x8283A98C,\n    0x8283AAF4,\n    0x8283AAF4,\n    0x8283A9C8,\n    0x8283A9DC,\n    0x8283A9E8,\n    0x8283AAA4,\n]\n\n[[switch]]\nbase = 0x82884554\nr = 11\ndefault = 0x828852A8\nlabels = [\n    0x82885468,\n    0x82884F58,\n    0x82884F58,\n    0x82884F58,\n    0x82884F58,\n    0x82884F04,\n    0x82884F14,\n    0x82884F30,\n    0x828852A8,\n    0x8288506C,\n    0x828850D8,\n    0x82885150,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828849C8,\n    0x828849C8,\n    0x828849C8,\n    0x8288457C,\n    0x828845E8,\n    0x82884640,\n    0x82884690,\n    0x828846E8,\n    0x82884738,\n    0x82884790,\n    0x828847E8,\n    0x82884848,\n    0x828848A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x82884908,\n    0x82884908,\n    0x82884908,\n    0x82884950,\n    0x82884950,\n    0x82884950,\n    0x82884DA4,\n    0x82884DA4,\n    0x82884DA4,\n    0x82884CF8,\n    0x82884CF8,\n    0x82884CF8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885368,\n    0x82885308,\n    0x82885310,\n    0x82885324,\n    0x82885340,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828852A8,\n    0x828851D8,\n    0x828851D8,\n    0x828851D8,\n    0x828851D8,\n    0x828851F4,\n    0x828851F4,\n    0x828851F4,\n    0x828851F4,\n    0x8288521C,\n    0x8288521C,\n    0x8288521C,\n    0x8288521C,\n]\n\n[[switch]]\nbase = 0x828934A8\nr = 10\ndefault = 0x828953D4\nlabels = [\n    0x828953B0,\n    0x82893FE8,\n    0x828953D4,\n    0x8289521C,\n    0x8289390C,\n    0x828934D0,\n    0x828942A4,\n    0x82894C08,\n    0x82894920,\n    0x82893DE8,\n    0x82893B64,\n    0x8289402C,\n    0x82894110,\n    0x82894A5C,\n    0x82894420,\n    0x82894500,\n    0x8289463C,\n    0x828950F8,\n    0x82895164,\n    0x828950A8,\n    0x828950D8,\n    0x82894F1C,\n    0x828953D4,\n    0x82895228,\n    0x82895298,\n    0x828952F8,\n]\n\n[[switch]]\nbase = 0x82895694\nr = 10\ndefault = 0x828977A0\nlabels = [\n    0x8289777C,\n    0x82896234,\n    0x828977A0,\n    0x828974F4,\n    0x82895B24,\n    0x828956BC,\n    0x828964F4,\n    0x82896E74,\n    0x82896B88,\n    0x8289601C,\n    0x82895D80,\n    0x82896278,\n    0x8289635C,\n    0x82896CC4,\n    0x82896680,\n    0x82896760,\n    0x828968A0,\n    0x82897384,\n    0x82897408,\n    0x82897338,\n    0x82897368,\n    0x8289719C,\n    0x828977A0,\n    0x82897500,\n    0x828975DC,\n    0x828976A4,\n]\n\n[[switch]]\nbase = 0x82897CC8\nr = 4\ndefault = 0x82897CB8\nlabels = [\n    0x828989EC,\n    0x82897C38,\n    0x82897CB8,\n    0x828989E0,\n    0x82897DF0,\n    0x82897CF0,\n    0x82898124,\n    0x82897F64,\n    0x82897ECC,\n    0x8289824C,\n    0x82898380,\n    0x82898098,\n    0x82898018,\n    0x82897DF0,\n    0x82898018,\n    0x82897DF0,\n    0x82897DF0,\n    0x828989DC,\n    0x82897E98,\n    0x828989E0,\n    0x828989E0,\n    0x828981B0,\n    0x82898018,\n    0x82898514,\n    0x82898618,\n    0x82898728,\n]\n\n[[switch]]\nbase = 0x828CDD48\nr = 11\ndefault = 0x828CE53C\nlabels = [\n    0x828CDE74,\n    0x828CDEF8,\n    0x828CDF7C,\n    0x828CDFFC,\n    0x828CE07C,\n    0x828CE100,\n    0x828CE184,\n    0x828CE204,\n    0x828CE284,\n    0x828CE300,\n    0x828CE378,\n    0x828CE388,\n    0x828CE53C,\n    0x828CE404,\n    0x828CE414,\n    0x828CE434,\n    0x828CE51C,\n]\n\n[[switch]]\nbase = 0x8291A1C4\nr = 28\ndefault = 0x8291A600\nlabels = [\n    0x8291A20C,\n    0x8291A224,\n    0x8291A238,\n    0x8291A268,\n    0x8291A2B0,\n    0x8291A2BC,\n    0x8291A2C8,\n    0x8291A2DC,\n    0x8291A2FC,\n    0x8291A324,\n    0x8291A338,\n    0x8291A344,\n    0x8291A350,\n    0x8291A36C,\n    0x8291A37C,\n    0x8291A388,\n    0x8291A388,\n    0x8291A390,\n    0x8291A3B4,\n    0x8291A388,\n    0x8291A3E4,\n    0x8291A400,\n    0x8291A388,\n    0x8291A388,\n    0x8291A410,\n    0x8291A428,\n    0x8291A388,\n    0x8291A474,\n    0x8291A48C,\n    0x8291A388,\n    0x8291A4A4,\n    0x8291A4C4,\n    0x8291A4DC,\n    0x8291A4FC,\n    0x8291A388,\n    0x8291A514,\n    0x8291A52C,\n    0x8291A388,\n    0x8291A550,\n    0x8291A388,\n    0x8291A580,\n    0x8291A388,\n    0x8291A5AC,\n    0x8291A388,\n    0x8291A5CC,\n    0x8291A5CC,\n    0x8291A5CC,\n]\n\n[[switch]]\nbase = 0x82926144\nr = 4\ndefault = 0x829266D8\nlabels = [\n    0x829261C4,\n    0x82926190,\n    0x82926190,\n    0x82926198,\n    0x82926190,\n    0x82926190,\n    0x829261C4,\n    0x829261AC,\n    0x829261CC,\n    0x829261E0,\n    0x82926190,\n    0x82926224,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x8292626C,\n    0x829262BC,\n    0x829262BC,\n    0x82926190,\n    0x829262F8,\n    0x82926190,\n    0x82926348,\n    0x82926374,\n    0x82926404,\n    0x82926190,\n    0x82926474,\n    0x829266D8,\n    0x829266D8,\n    0x829264C4,\n    0x829264DC,\n    0x82926190,\n    0x829264E4,\n    0x82926534,\n    0x82926564,\n    0x82926588,\n    0x829265CC,\n    0x82926190,\n    0x82926190,\n    0x82926610,\n    0x82926624,\n    0x82926624,\n    0x82926648,\n    0x82926190,\n    0x82926190,\n    0x8292666C,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x82926678,\n    0x829266A4,\n    0x829266A4,\n    0x829266A4,\n]\n\n[[switch]]\nbase = 0x82932418\nr = 11\ndefault = 0x82938F8C\nlabels = [\n    0x82932440,\n    0x829324FC,\n    0x82932558,\n    0x82932890,\n    0x8293291C,\n    0x82932964,\n    0x829329C0,\n    0x82932A10,\n    0x82932B24,\n    0x82932BF0,\n    0x82932C14,\n    0x82932C70,\n    0x82932F28,\n    0x8293308C,\n    0x829330E4,\n    0x82933134,\n    0x829331DC,\n    0x82933B10,\n    0x82933C90,\n    0x82933CF0,\n    0x82933D98,\n    0x82933ECC,\n    0x82933F14,\n    0x8293413C,\n    0x82934220,\n    0x8293445C,\n    0x829344AC,\n    0x829348F0,\n    0x82934B7C,\n    0x82934E2C,\n    0x829350AC,\n    0x82935354,\n    0x82935408,\n    0x82935504,\n    0x82935554,\n    0x8293581C,\n    0x8293593C,\n    0x82935A40,\n    0x82935A84,\n    0x82935ADC,\n    0x82935B34,\n    0x82935F98,\n    0x82935F98,\n    0x82935F98,\n    0x82936030,\n    0x829360B8,\n    0x829360B8,\n    0x82936030,\n    0x829360B8,\n    0x829360B8,\n    0x829366EC,\n    0x82936724,\n    0x82936938,\n    0x82936978,\n    0x82936A10,\n    0x82936BB0,\n    0x8293704C,\n    0x8293721C,\n    0x82937260,\n    0x82937374,\n    0x8293755C,\n    0x829375A4,\n    0x82937644,\n    0x8293794C,\n    0x82937C7C,\n    0x82937D34,\n    0x82937D98,\n    0x82937EE0,\n    0x82938294,\n    0x82938400,\n    0x829384B4,\n    0x82938530,\n    0x829385AC,\n    0x82938628,\n    0x829386A0,\n    0x82938778,\n    0x829387F4,\n    0x82938870,\n    0x829388EC,\n    0x82938964,\n    0x82938A3C,\n    0x82938AB8,\n    0x82938B34,\n    0x82938BB0,\n    0x82938C28,\n    0x82938D00,\n    0x82938D7C,\n    0x82938DF8,\n    0x82938E74,\n    0x82938EF8,\n]\n\n[[switch]]\nbase = 0x8293BD90\nr = 10\ndefault = 0x8293D84C\nlabels = [\n    0x8293BDB8,\n    0x8293BDB8,\n    0x8293BFF4,\n    0x8293C048,\n    0x8293C08C,\n    0x8293C190,\n    0x8293C1DC,\n    0x8293CA94,\n    0x8293CAE0,\n    0x8293CB94,\n    0x8293C220,\n    0x8293C274,\n    0x8293C328,\n    0x8293C374,\n    0x8293C3CC,\n    0x8293C41C,\n    0x8293C470,\n    0x8293C5AC,\n    0x8293C6E8,\n    0x8293C8C0,\n    0x8293BEB4,\n    0x8293BFA0,\n    0x8293CDD8,\n    0x8293CE0C,\n    0x8293CE9C,\n    0x8293D0B8,\n    0x8293D124,\n]\n\n[[switch]]\nbase = 0x82944F00\nr = 9\ndefault = 0x82944F60\nlabels = [\n    0x82944F68,\n    0x82944F70,\n    0x82944F78,\n    0x82944F80,\n    0x82944F88,\n    0x82944F90,\n    0x82944F98,\n    0x82944FA0,\n    0x82944FA8,\n    0x82944FB0,\n    0x82944FB8,\n    0x82944FC4,\n    0x82944FCC,\n    0x82944FD4,\n    0x82944FDC,\n]\n\n[[switch]]\nbase = 0x8294B7CC\nr = 11\ndefault = 0x8294BC6C\nlabels = [\n    0x8294B7F4,\n    0x8294B83C,\n    0x8294B870,\n    0x8294B870,\n    0x8294BC6C,\n    0x8294BC6C,\n    0x8294B8DC,\n    0x8294B974,\n    0x8294BA40,\n    0x8294BC6C,\n    0x8294B9E8,\n    0x8294BAAC,\n    0x8294B870,\n    0x8294B870,\n]\n\n[[switch]]\nbase = 0x8294C490\nr = 4\ndefault = 0x8294CC50\nlabels = [\n    0x8294C5A8,\n    0x8294C4DC,\n    0x8294C4DC,\n    0x8294C4E4,\n    0x8294C4DC,\n    0x8294C4DC,\n    0x8294C4F8,\n    0x8294C5A8,\n    0x8294C590,\n    0x8294C5B0,\n    0x8294C5C4,\n    0x8294C4DC,\n    0x8294C5D0,\n    0x8294C4DC,\n    0x8294C5E4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C5F4,\n    0x8294C648,\n    0x8294C648,\n    0x8294C684,\n    0x8294C694,\n    0x8294C6A4,\n    0x8294C6B4,\n    0x8294C6CC,\n    0x8294C5F4,\n    0x8294C6FC,\n    0x8294C5F4,\n    0x8294C6FC,\n    0x8294C5F4,\n    0x8294C684,\n    0x8294C6A4,\n    0x8294C6CC,\n    0x8294C648,\n    0x8294C6DC,\n    0x8294C768,\n    0x8294C798,\n    0x8294C7C4,\n    0x8294C7EC,\n    0x8294C878,\n    0x8294C4DC,\n    0x8294C89C,\n    0x8294C4DC,\n    0x8294C4DC,\n    0x8294C4DC,\n    0x8294C8E4,\n    0x8294C4DC,\n    0x8294C934,\n    0x8294C960,\n    0x8294C98C,\n    0x8294C4DC,\n    0x8294C9D4,\n    0x8294CC50,\n    0x8294CC50,\n    0x8294CA24,\n    0x8294CA3C,\n    0x8294C4DC,\n    0x8294CA44,\n    0x8294CAA4,\n    0x8294CAD4,\n    0x8294CAF8,\n    0x8294CB3C,\n    0x8294C4DC,\n    0x8294C4DC,\n    0x8294CB80,\n    0x8294CB94,\n    0x8294CB94,\n    0x8294CBB8,\n    0x8294C4DC,\n    0x8294C4DC,\n    0x8294CBDC,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CC50,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CBE8,\n    0x8294CC1C,\n    0x8294CC1C,\n    0x8294CC1C,\n]\n\n[[switch]]\nbase = 0x8294CE3C\nr = 11\ndefault = 0x8294D290\nlabels = [\n    0x8294CE64,\n    0x8294CE6C,\n    0x8294CE78,\n    0x8294CE84,\n    0x8294CE90,\n    0x8294CE9C,\n    0x8294CEA8,\n    0x8294CEB4,\n    0x8294CEBC,\n    0x8294CEC8,\n    0x8294CED4,\n    0x8294CEE0,\n    0x8294CEEC,\n    0x8294CEF8,\n    0x8294CF04,\n    0x8294CF10,\n    0x8294CF1C,\n    0x8294CF28,\n    0x8294CF34,\n    0x8294CF40,\n    0x8294CF4C,\n    0x8294CF58,\n    0x8294CF64,\n    0x8294CF70,\n    0x8294CF7C,\n    0x8294CF88,\n    0x8294CF94,\n    0x8294CFA0,\n    0x8294CFAC,\n    0x8294CFB8,\n    0x8294CFC4,\n    0x8294CFD0,\n    0x8294CFDC,\n    0x8294CFE8,\n    0x8294CFF4,\n    0x8294D000,\n    0x8294D00C,\n    0x8294D018,\n    0x8294D024,\n    0x8294D030,\n    0x8294D03C,\n    0x8294D048,\n    0x8294D054,\n    0x8294D060,\n    0x8294D06C,\n    0x8294D078,\n    0x8294D084,\n    0x8294D090,\n    0x8294D09C,\n    0x8294D0A8,\n    0x8294D0B4,\n    0x8294D0C0,\n    0x8294D0CC,\n    0x8294D0D8,\n    0x8294D0E4,\n    0x8294D0F0,\n    0x8294D0FC,\n    0x8294D108,\n    0x8294D114,\n    0x8294D120,\n    0x8294D12C,\n    0x8294D138,\n    0x8294D144,\n    0x8294D150,\n    0x8294D15C,\n    0x8294D164,\n    0x8294D16C,\n    0x8294D178,\n    0x8294D184,\n    0x8294D190,\n    0x8294D19C,\n    0x8294D1A8,\n    0x8294D1B4,\n    0x8294D1C0,\n    0x8294D1CC,\n    0x8294D1D8,\n    0x8294D1E0,\n    0x8294D1E8,\n    0x8294D1F0,\n    0x8294D1F8,\n    0x8294D200,\n    0x8294D208,\n    0x8294D210,\n    0x8294D218,\n    0x8294D220,\n    0x8294D228,\n    0x8294D230,\n    0x8294D238,\n    0x8294D240,\n    0x8294D240,\n    0x8294D248,\n    0x8294D250,\n    0x8294D258,\n    0x8294D260,\n    0x8294D268,\n    0x8294D270,\n    0x8294D278,\n    0x8294D280,\n]\n\n[[switch]]\nbase = 0x82954EA4\nr = 30\ndefault = 0x82955510\nlabels = [\n    0x82955508,\n    0x82954ECC,\n    0x82954ED4,\n    0x82954EDC,\n    0x82954EE4,\n    0x82954EEC,\n    0x82954EF4,\n    0x82954EFC,\n    0x82954F04,\n    0x82954F0C,\n    0x82954F14,\n    0x82954F1C,\n    0x82954F24,\n    0x82954F2C,\n    0x82954F34,\n    0x82955510,\n    0x82955510,\n    0x82954F3C,\n    0x82954F44,\n]\n\n[[switch]]\nbase = 0x8298F460\nr = 11\ndefault = 0x829909A8\nlabels = [\n    0x8298F494,\n    0x8298F4C8,\n    0x8298F53C,\n    0x8298F584,\n    0x8298F5D4,\n    0x8298F648,\n    0x8298F67C,\n    0x8298F6BC,\n    0x8298F6F0,\n    0x829909A8,\n    0x8298F750,\n    0x8298F784,\n    0x8298F7B8,\n    0x8298F818,\n    0x8298F844,\n    0x8298F870,\n    0x8298F8AC,\n    0x8298FA08,\n    0x8298FA50,\n    0x8298FA9C,\n    0x8298FAD0,\n    0x8298FB04,\n    0x8298FB44,\n    0x8298FBC0,\n    0x8298FBF4,\n    0x8298FC34,\n    0x829909A8,\n    0x8298FC90,\n    0x8298FCBC,\n    0x8298FD14,\n    0x8298FD6C,\n    0x8298FDC4,\n    0x8298FE14,\n    0x8298FE48,\n    0x8298FE9C,\n    0x8298FEF4,\n    0x8298FF58,\n    0x8298FFD4,\n    0x82990050,\n    0x82990098,\n    0x829909A8,\n    0x829900E0,\n    0x829900E0,\n    0x829900E0,\n    0x8299011C,\n    0x82990158,\n    0x82990158,\n    0x8299011C,\n    0x82990158,\n    0x82990158,\n    0x829909A8,\n    0x8299023C,\n    0x829902C0,\n    0x8299032C,\n    0x82990368,\n    0x829903E8,\n    0x829904B4,\n    0x829904F4,\n    0x8299058C,\n    0x829905EC,\n    0x82990654,\n    0x829909A8,\n    0x82990688,\n    0x829906BC,\n    0x82990764,\n    0x829907C8,\n    0x82990828,\n    0x8299085C,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x829909A8,\n    0x82990890,\n    0x8299090C,\n]\n\n[[switch]]\nbase = 0x829969EC\nr = 11\ndefault = 0x82999E48\nlabels = [\n    0x82996A14,\n    0x82996A1C,\n    0x82996BC0,\n    0x82996BC8,\n    0x82996BD0,\n    0x82996BD8,\n    0x82996BE0,\n    0x82996BE8,\n    0x82996BF0,\n    0x82996BF8,\n    0x82999E34,\n    0x82999E48,\n    0x82999E48,\n    0x82999E40,\n]\n\n[[switch]]\nbase = 0x82996C28\nr = 11\ndefault = 0x82999CD0\nlabels = [\n    0x82996C50,\n    0x82996D5C,\n    0x82996E68,\n    0x82997418,\n    0x82997734,\n    0x82997948,\n    0x82997BE0,\n    0x82997C68,\n    0x82997CF0,\n    0x82999CD0,\n    0x82999CD0,\n    0x82997F88,\n    0x82998010,\n    0x8299811C,\n    0x82998228,\n    0x82998334,\n    0x82999CD0,\n    0x829985DC,\n    0x829987F0,\n    0x829990C8,\n    0x8299970C,\n    0x82999920,\n    0x82999C4C,\n]\n\n[[switch]]\nbase = 0x8299BB54\nr = 27\ndefault = 0x8299D29C\nlabels = [\n    0x8299BB98,\n    0x8299BBDC,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC48,\n    0x8299BC60,\n    0x8299BC88,\n    0x8299BCA4,\n    0x8299BCC0,\n    0x8299BCDC,\n    0x8299BCF8,\n    0x8299BC58,\n    0x8299BD14,\n    0x8299BC58,\n    0x8299BC48,\n    0x8299BD3C,\n    0x8299BD58,\n    0x8299BD74,\n    0x8299BD90,\n    0x8299BDE8,\n    0x8299D29C,\n    0x8299D29C,\n    0x8299BE00,\n    0x8299BE2C,\n    0x8299BE5C,\n    0x8299BE70,\n    0x8299BC58,\n    0x8299BEAC,\n    0x8299BED8,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BF24,\n    0x8299BF8C,\n    0x8299BC58,\n    0x8299BFFC,\n    0x8299BC58,\n    0x8299C010,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299C024,\n    0x8299C040,\n    0x8299C070,\n    0x8299C08C,\n    0x8299C0A8,\n    0x8299C0C4,\n    0x8299C0E0,\n    0x8299C104,\n    0x8299C118,\n    0x8299C140,\n    0x8299C158,\n    0x8299C178,\n    0x8299C194,\n    0x8299C1B0,\n    0x8299C1CC,\n    0x8299C1E8,\n    0x8299C204,\n    0x8299C220,\n    0x8299C23C,\n    0x8299C258,\n    0x8299C274,\n    0x8299C290,\n    0x8299C2AC,\n    0x8299C2C8,\n    0x8299C2E4,\n    0x8299C300,\n    0x8299CA6C,\n    0x8299BE54,\n    0x8299C31C,\n    0x8299C330,\n    0x8299BC58,\n    0x8299C338,\n    0x8299C348,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299C360,\n    0x8299C388,\n    0x8299C3C8,\n    0x8299C3D8,\n    0x8299C3D8,\n    0x8299C3D8,\n    0x8299BC58,\n    0x8299C3F8,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299C408,\n    0x8299C42C,\n    0x8299C450,\n    0x8299C478,\n    0x8299CA6C,\n    0x8299BE54,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299C4C0,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299C3C8,\n    0x8299C4C8,\n    0x8299C4F4,\n    0x8299C520,\n    0x8299C540,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299C560,\n    0x8299C584,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299C59C,\n    0x8299BC58,\n    0x8299C5B8,\n    0x8299C5D8,\n    0x8299C6B4,\n    0x8299C610,\n    0x8299C5F8,\n    0x8299C708,\n    0x8299CA6C,\n    0x8299CA6C,\n    0x8299BE54,\n    0x8299C730,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299C560,\n    0x8299C73C,\n    0x8299C758,\n    0x8299D29C,\n    0x8299D29C,\n    0x8299BC58,\n    0x8299BC48,\n    0x8299C78C,\n    0x8299C7DC,\n    0x8299C830,\n    0x8299C84C,\n    0x8299C868,\n    0x8299C884,\n    0x8299BC58,\n    0x8299C8A0,\n    0x8299C928,\n    0x8299C9B0,\n    0x8299CA6C,\n    0x8299BE54,\n    0x8299CA5C,\n    0x8299C3C8,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299CA74,\n    0x8299CA6C,\n    0x8299BE54,\n    0x8299CAE8,\n    0x8299CB04,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299CB0C,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299CBA8,\n    0x8299CAFC,\n    0x8299CA6C,\n    0x8299BE54,\n    0x8299CBB0,\n    0x8299C3C8,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299CA6C,\n    0x8299CBB8,\n    0x8299CBCC,\n    0x8299CBDC,\n    0x8299CBE4,\n    0x8299CC00,\n    0x8299CC20,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299CC2C,\n    0x8299CC3C,\n    0x8299BC58,\n    0x8299CC5C,\n    0x8299BC58,\n    0x8299CC2C,\n    0x8299CC3C,\n    0x8299CC7C,\n    0x8299CC94,\n    0x8299CC9C,\n    0x8299CA6C,\n    0x8299CBB8,\n    0x8299BC58,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299CCA4,\n    0x8299CCC4,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299CCE4,\n    0x8299CDAC,\n    0x8299CEAC,\n    0x8299CEAC,\n    0x8299CEBC,\n    0x8299CECC,\n    0x8299BC58,\n    0x8299CEE0,\n    0x8299CEE0,\n    0x8299CEFC,\n    0x8299CF14,\n    0x8299CA6C,\n    0x8299CF28,\n    0x8299CF38,\n    0x8299CF38,\n    0x8299CF40,\n    0x8299CF5C,\n    0x8299CF6C,\n    0x8299CF74,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299CF84,\n    0x8299CF98,\n    0x8299CFAC,\n    0x8299CFC8,\n    0x8299BC58,\n    0x8299CFD0,\n    0x8299CFD8,\n    0x8299CFE0,\n    0x8299CFE8,\n    0x8299CFF0,\n    0x8299BC58,\n    0x8299CFF8,\n    0x8299BC58,\n    0x8299D000,\n    0x8299D00C,\n    0x8299D018,\n    0x8299BC58,\n    0x8299D024,\n    0x8299D030,\n    0x8299BC58,\n    0x8299D03C,\n    0x8299D048,\n    0x8299D054,\n    0x8299D060,\n    0x8299BC58,\n    0x8299D06C,\n    0x8299D078,\n    0x8299BC58,\n    0x8299D084,\n    0x8299BC58,\n    0x8299D090,\n    0x8299BC58,\n    0x8299D0B4,\n    0x8299D0C0,\n    0x8299D0CC,\n    0x8299D0D8,\n    0x8299D0E4,\n    0x8299D0F0,\n    0x8299BC58,\n    0x8299D09C,\n    0x8299D0FC,\n    0x8299D11C,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299D16C,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299BC2C,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299D18C,\n    0x8299BC58,\n    0x8299D1E0,\n    0x8299BC58,\n    0x8299D1F8,\n    0x8299BC58,\n    0x8299D210,\n    0x8299BC58,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299D244,\n    0x8299D244,\n    0x8299CA6C,\n    0x8299BC58,\n    0x8299BC58,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299BC58,\n    0x8299D258,\n    0x8299D224,\n    0x8299D224,\n    0x8299D224,\n    0x8299D26C,\n    0x8299D284,\n    0x8299D294,\n]\n\n[[switch]]\nbase = 0x8299D474\nr = 11\ndefault = 0x8299E1F4\nlabels = [\n    0x8299D49C,\n    0x8299D4A4,\n    0x8299D4B0,\n    0x8299D4BC,\n    0x8299D4C8,\n    0x8299D4D0,\n    0x8299D4DC,\n    0x8299D4E8,\n    0x8299D4F4,\n    0x8299D500,\n    0x8299D50C,\n    0x8299D518,\n    0x8299D524,\n    0x8299D530,\n    0x8299D538,\n    0x8299D540,\n    0x8299D548,\n    0x8299D550,\n    0x8299D558,\n    0x8299D560,\n    0x8299D56C,\n    0x8299D578,\n    0x8299D584,\n    0x8299D590,\n    0x8299D598,\n    0x8299D5A0,\n    0x8299D5A8,\n    0x8299D5B4,\n    0x8299D5C0,\n    0x8299D5CC,\n    0x8299D5D8,\n    0x8299D5E4,\n    0x8299D5F0,\n    0x8299D5FC,\n    0x8299D608,\n    0x8299D614,\n    0x8299D620,\n    0x8299D62C,\n    0x8299D638,\n    0x8299D644,\n    0x8299D650,\n    0x8299D65C,\n    0x8299D668,\n    0x8299D674,\n    0x8299D67C,\n    0x8299D688,\n    0x8299D694,\n    0x8299D6A0,\n    0x8299D6AC,\n    0x8299D6B4,\n    0x8299D6BC,\n    0x8299D6C4,\n    0x8299D6CC,\n    0x8299D6D4,\n    0x8299D6DC,\n    0x8299D6E8,\n    0x8299D6F0,\n    0x8299D6FC,\n    0x8299D704,\n    0x8299D70C,\n    0x8299D714,\n    0x8299D71C,\n    0x8299D724,\n    0x8299D72C,\n    0x8299D734,\n    0x8299D73C,\n    0x8299D744,\n    0x8299D74C,\n    0x8299D754,\n    0x8299D75C,\n    0x8299D764,\n    0x8299D76C,\n    0x8299D774,\n    0x8299D77C,\n    0x8299D788,\n    0x8299D794,\n    0x8299D7A0,\n    0x8299D7AC,\n    0x8299D7B8,\n    0x8299D7C4,\n    0x8299D7CC,\n    0x8299D7D8,\n    0x8299D7E4,\n    0x8299D7F0,\n    0x8299D7FC,\n    0x8299D804,\n    0x8299D810,\n    0x8299D81C,\n    0x8299D828,\n    0x8299D834,\n    0x8299D840,\n    0x8299D848,\n    0x8299D854,\n    0x8299D860,\n    0x8299D86C,\n    0x8299D878,\n    0x8299D884,\n    0x8299D890,\n    0x8299D89C,\n    0x8299D8A4,\n    0x8299D8B0,\n    0x8299D8B8,\n    0x8299D8C4,\n    0x8299D8D0,\n    0x8299D8D8,\n    0x8299D8E4,\n    0x8299D8F0,\n    0x8299D8FC,\n    0x8299D908,\n    0x8299D914,\n    0x8299D920,\n    0x8299D92C,\n    0x8299D934,\n    0x8299D940,\n    0x8299D94C,\n    0x8299D958,\n    0x8299D964,\n    0x8299D970,\n    0x8299D97C,\n    0x8299D988,\n    0x8299D994,\n    0x8299D9A0,\n    0x8299D9AC,\n    0x8299D9B8,\n    0x8299D9C4,\n    0x8299D9D0,\n    0x8299D9DC,\n    0x8299D9E8,\n    0x8299D9F4,\n    0x8299DA00,\n    0x8299DA0C,\n    0x8299DA14,\n    0x8299DA1C,\n    0x8299DA28,\n    0x8299DA34,\n    0x8299DA40,\n    0x8299DA4C,\n    0x8299DA58,\n    0x8299DA64,\n    0x8299DA70,\n    0x8299DA7C,\n    0x8299DA88,\n    0x8299DA90,\n    0x8299DA98,\n    0x8299DAA0,\n    0x8299DAA8,\n    0x8299DAB4,\n    0x8299DAC0,\n    0x8299DACC,\n    0x8299DAD8,\n    0x8299DAE4,\n    0x8299DAF0,\n    0x8299DAF8,\n    0x8299DB00,\n    0x8299DB0C,\n    0x8299DB18,\n    0x8299DB24,\n    0x8299DB30,\n    0x8299DB3C,\n    0x8299DB44,\n    0x8299DB4C,\n    0x8299DB58,\n    0x8299DB64,\n    0x8299DB70,\n    0x8299DB78,\n    0x8299DB84,\n    0x8299DB8C,\n    0x8299DB94,\n    0x8299DBA0,\n    0x8299DBAC,\n    0x8299DBB4,\n    0x8299DBBC,\n    0x8299DBC8,\n    0x8299DBD4,\n    0x8299DBDC,\n    0x8299DBE8,\n    0x8299DBF0,\n    0x8299DBFC,\n    0x8299DC08,\n    0x8299DC14,\n    0x8299DC1C,\n    0x8299DC28,\n    0x8299DC34,\n    0x8299DC40,\n    0x8299DC4C,\n    0x8299DC58,\n    0x8299DC64,\n    0x8299DC70,\n    0x8299DC7C,\n    0x8299DC88,\n    0x8299DC94,\n    0x8299DCA0,\n    0x8299DCAC,\n    0x8299DCB8,\n    0x8299DCC0,\n    0x8299DCC8,\n    0x8299DCD4,\n    0x8299DCE0,\n    0x8299DCE8,\n    0x8299DCF4,\n    0x8299DCFC,\n    0x8299DD08,\n    0x8299DD14,\n    0x8299DD20,\n    0x8299DD2C,\n    0x8299DD38,\n    0x8299DD44,\n    0x8299DD50,\n    0x8299DD5C,\n    0x8299DD64,\n    0x8299DD6C,\n    0x8299DD78,\n    0x8299DD84,\n    0x8299DD90,\n    0x8299DD9C,\n    0x8299DDA8,\n    0x8299DDB4,\n    0x8299DDC0,\n    0x8299DDCC,\n    0x8299DDD8,\n    0x8299DDE4,\n    0x8299DDF0,\n    0x8299DDFC,\n    0x8299DE08,\n    0x8299DE14,\n    0x8299DE20,\n    0x8299DE2C,\n    0x8299DE38,\n    0x8299DE44,\n    0x8299DE50,\n    0x8299DE5C,\n    0x8299DE68,\n    0x8299DE74,\n    0x8299DE80,\n    0x8299DE8C,\n    0x8299DE98,\n    0x8299DEA4,\n    0x8299DEB0,\n    0x8299DEBC,\n    0x8299DEC8,\n    0x8299DED4,\n    0x8299DEE0,\n    0x8299DEEC,\n    0x8299DEF8,\n    0x8299DF04,\n    0x8299DF10,\n    0x8299DF1C,\n    0x8299DF28,\n    0x8299DF34,\n    0x8299DF40,\n    0x8299DF4C,\n    0x8299DF58,\n    0x8299DF64,\n    0x8299DF70,\n    0x8299DF7C,\n    0x8299DF88,\n    0x8299DF94,\n    0x8299DFA0,\n    0x8299DFAC,\n    0x8299DFB8,\n    0x8299DFC4,\n    0x8299DFD0,\n    0x8299DFDC,\n    0x8299DFE8,\n    0x8299DFF4,\n    0x8299E000,\n    0x8299E00C,\n    0x8299E018,\n    0x8299E024,\n    0x8299E030,\n    0x8299E03C,\n    0x8299E048,\n    0x8299E054,\n    0x8299E05C,\n    0x8299E068,\n    0x8299E074,\n    0x8299E080,\n    0x8299E08C,\n    0x8299E098,\n    0x8299E0A4,\n    0x8299E0B0,\n    0x8299E0BC,\n    0x8299E0C8,\n    0x8299E0D4,\n    0x8299E0E0,\n    0x8299E0EC,\n    0x8299E0F8,\n    0x8299E104,\n    0x8299E110,\n    0x8299E11C,\n    0x8299E124,\n    0x8299E12C,\n    0x8299E134,\n    0x8299E140,\n    0x8299E14C,\n    0x8299E154,\n    0x8299E160,\n    0x8299E16C,\n    0x8299E174,\n    0x8299E17C,\n    0x8299E184,\n    0x8299E18C,\n    0x8299E194,\n    0x8299E19C,\n    0x8299E1A4,\n    0x8299E1B0,\n    0x8299E1BC,\n    0x8299E1C4,\n    0x8299E1CC,\n    0x8299E1D4,\n    0x8299E1DC,\n    0x8299E1E4,\n]\n\n[[switch]]\nbase = 0x829CA8C8\nr = 11\ndefault = 0x829CAD08\nlabels = [\n    0x829CA8F0,\n    0x829CA958,\n    0x829CAB30,\n    0x829CAB20,\n    0x829CAB20,\n    0x829CABBC,\n    0x829CAD08,\n    0x829CAD08,\n    0x829CAD08,\n    0x829CAD08,\n    0x829CAD08,\n    0x829CAD08,\n    0x829CAD08,\n    0x829CAB5C,\n    0x829CAB20,\n    0x829CAC20,\n    0x829CAD08,\n    0x829CAA88,\n    0x829CAAD8,\n    0x829CAB20,\n]\n\n[[switch]]\nbase = 0x829D0BD0\nr = 29\ndefault = 0x829D12E4\nlabels = [\n    0x829D0BF8,\n    0x829D0C54,\n    0x829D0C28,\n    0x829D0DA0,\n    0x829D0DC8,\n    0x829D0DE4,\n    0x829D0E00,\n    0x829D0E1C,\n    0x829D0F04,\n    0x829D1044,\n    0x829D10A0,\n    0x829D10BC,\n    0x829D1214,\n]\n\n[[switch]]\nbase = 0x829D80B0\nr = 10\ndefault = 0x829D89BC\nlabels = [\n    0x829D8424,\n    0x829D8508,\n    0x829D8340,\n    0x829D844C,\n    0x829D8488,\n    0x829D83E0,\n    0x829D89BC,\n    0x829D89BC,\n    0x829D89BC,\n    0x829D89BC,\n    0x829D89BC,\n    0x829D89BC,\n    0x829D89BC,\n    0x829D8494,\n    0x829D8488,\n    0x829D80D8,\n    0x829D86D8,\n    0x829D8270,\n    0x829D8314,\n    0x829D86D0,\n    0x829D880C,\n]\n\n[[switch]]\nbase = 0x829D90AC\nr = 11\ndefault = 0x829D95E0\nlabels = [\n    0x829D95E0,\n    0x829D90D4,\n    0x829D9130,\n    0x829D913C,\n    0x829D9148,\n    0x829D9154,\n    0x829D9160,\n    0x829D916C,\n    0x829D9178,\n    0x829D9184,\n    0x829D9190,\n    0x829D919C,\n    0x829D91A8,\n    0x829D91B8,\n    0x829D91F0,\n    0x829D9208,\n    0x829D91C0,\n    0x829D91CC,\n    0x829D948C,\n    0x829D91FC,\n    0x829D9214,\n    0x829D9220,\n    0x829D9230,\n    0x829D9238,\n    0x829D9240,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D92F8,\n    0x829D9498,\n    0x829D94A4,\n    0x829D94B0,\n    0x829D94DC,\n    0x829D94E8,\n    0x829D94B0,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9464,\n    0x829D9248,\n    0x829D9454,\n    0x829D945C,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D91D8,\n    0x829D91E4,\n    0x829D95E0,\n    0x829D945C,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D95E0,\n    0x829D9258,\n    0x829D92C0,\n    0x829D9464,\n]\n\n[[switch]]\nbase = 0x829F4C5C\nr = 11\ndefault = 0x829F51A0\nlabels = [\n    0x829F4C84,\n    0x829F4C84,\n    0x829F500C,\n    0x829F500C,\n    0x829F5054,\n    0x829F505C,\n    0x829F5064,\n    0x829F506C,\n    0x829F50F8,\n    0x829F5100,\n    0x829F5108,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F51A0,\n    0x829F5074,\n    0x829F50E0,\n    0x829F50E8,\n    0x829F50F0,\n]\n\n[[switch]]\nbase = 0x82A15A34\nr = 11\ndefault = 0x82A1673C\nlabels = [\n    0x82A15A5C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A15B18,\n    0x82A15B18,\n    0x82A15B18,\n    0x82A1673C,\n    0x82A15C80,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A15DD0,\n    0x82A160E0,\n    0x82A15DD8,\n    0x82A160E8,\n    0x82A162C4,\n    0x82A15DE0,\n    0x82A15DE8,\n    0x82A1633C,\n    0x82A16344,\n    0x82A1634C,\n    0x82A16354,\n    0x82A165F8,\n    0x82A16600,\n    0x82A16608,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A15DD0,\n    0x82A160E0,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A1635C,\n    0x82A16364,\n    0x82A1636C,\n    0x82A1636C,\n    0x82A164E4,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A15F74,\n    0x82A15F74,\n    0x82A15F6C,\n    0x82A15F6C,\n    0x82A15F6C,\n    0x82A15F6C,\n    0x82A1673C,\n    0x82A1673C,\n    0x82A160D8,\n]\n\n[[switch]]\nbase = 0x82A239D8\nr = 11\ndefault = 0x82A24A04\nlabels = [\n    0x82A23A00,\n    0x82A23F58,\n    0x82A23F90,\n    0x82A242D8,\n    0x82A24634,\n    0x82A24664,\n    0x82A247B0,\n    0x82A2485C,\n]\n\n[[switch]]\nbase = 0x82A2573C\nr = 11\ndefault = 0x82A2567C\nlabels = [\n    0x82A258D8,\n    0x82A25C34,\n    0x82A25C34,\n    0x82A2567C,\n    0x82A2567C,\n    0x82A25778,\n    0x82A25D28,\n    0x82A25D28,\n    0x82A25D28,\n    0x82A25764,\n    0x82A2567C,\n    0x82A2567C,\n    0x82A2567C,\n    0x82A2567C,\n    0x82A25D88,\n    0x82A25E8C,\n]\n\n[[switch]]\nbase = 0x82A3FBF4\nr = 11\ndefault = 0x82A47110\nlabels = [\n    0x82A3FC1C,\n    0x82A3FC38,\n    0x82A3FCA0,\n    0x82A3FCA0,\n    0x82A3FC1C,\n    0x82A3FC1C,\n    0x82A3FC58,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FC90,\n    0x82A3FCC0,\n    0x82A3FCC0,\n    0x82A3FC6C,\n    0x82A3FCE4,\n    0x82A3FDE8,\n    0x82A3FE4C,\n    0x82A3FCF4,\n    0x82A47104,\n]\n\n[[switch]]\nbase = 0x82A405B8\nr = 11\ndefault = 0x82A40678\nlabels = [\n    0x82A405E0,\n    0x82A405F8,\n    0x82A40678,\n    0x82A40678,\n    0x82A40610,\n    0x82A40610,\n    0x82A40610,\n    0x82A40610,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A40678,\n    0x82A40678,\n    0x82A40624,\n    0x82A4064C,\n    0x82A40624,\n    0x82A4064C,\n    0x82A4738C,\n    0x82A40624,\n    0x82A40624,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A40624,\n    0x82A40624,\n    0x82A40624,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A4064C,\n    0x82A40678,\n    0x82A4064C,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A473AC,\n    0x82A4064C,\n    0x82A40678,\n    0x82A4065C,\n    0x82A4065C,\n    0x82A4065C,\n    0x82A4065C,\n    0x82A4065C,\n    0x82A4065C,\n    0x82A4064C,\n    0x82A4064C,\n]\n\n[[switch]]\nbase = 0x82A41528\nr = 11\ndefault = 0x82A478D8\nlabels = [\n    0x82A4156C,\n    0x82A41550,\n    0x82A419E0,\n    0x82A419E0,\n    0x82A41928,\n    0x82A41920,\n    0x82A478D8,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41B78,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41D04,\n    0x82A41B04,\n    0x82A41B04,\n    0x82A42068,\n    0x82A4172C,\n    0x82A42504,\n    0x82A469C8,\n    0x82A425DC,\n]\n\n[[switch]]\nbase = 0x82A41E34\nr = 11\ndefault = 0x82A475AC\nlabels = [\n    0x82A41E5C,\n    0x82A41E5C,\n    0x82A41E5C,\n    0x82A41E70,\n    0x82A41EA8,\n    0x82A41EC8,\n    0x82A41ED8,\n    0x82A41EE4,\n    0x82A41EF4,\n    0x82A41F00,\n    0x82A41F14,\n    0x82A41E5C,\n    0x82A41E84,\n]\n\n[[switch]]\nbase = 0x82A425F0\nr = 22\ndefault = 0x82A4781C\nlabels = [\n    0x82A42618,\n    0x82A42620,\n    0x82A42940,\n    0x82A42940,\n    0x82A42620,\n    0x82A42BFC,\n    0x82A42BFC,\n    0x82A43068,\n    0x82A43178,\n    0x82A43260,\n    0x82A433CC,\n    0x82A43580,\n    0x82A436E4,\n    0x82A466EC,\n    0x82A466EC,\n    0x82A437D0,\n    0x82A439E8,\n    0x82A4436C,\n    0x82A4452C,\n    0x82A4466C,\n    0x82A44C98,\n    0x82A44C04,\n    0x82A446F0,\n    0x82A44E88,\n    0x82A44848,\n    0x82A44E90,\n    0x82A44FFC,\n    0x82A46854,\n    0x82A449E4,\n    0x82A44AA8,\n    0x82A44B38,\n    0x82A44D24,\n    0x82A45178,\n    0x82A452A0,\n    0x82A453A8,\n    0x82A44DB4,\n    0x82A44DE0,\n    0x82A44BFC,\n    0x82A44F18,\n    0x82A44F20,\n    0x82A4557C,\n    0x82A45690,\n    0x82A45690,\n    0x82A45690,\n    0x82A45690,\n    0x82A458A0,\n    0x82A458A0,\n    0x82A45690,\n    0x82A458A0,\n    0x82A458A0,\n    0x82A4781C,\n    0x82A45C24,\n    0x82A45CD0,\n    0x82A4387C,\n    0x82A45D70,\n    0x82A45E88,\n    0x82A46184,\n    0x82A44C0C,\n    0x82A43100,\n    0x82A4626C,\n    0x82A433CC,\n    0x82A43420,\n    0x82A43580,\n    0x82A46330,\n    0x82A44C14,\n    0x82A464F4,\n    0x82A433CC,\n    0x82A43580,\n    0x82A466A4,\n    0x82A466A4,\n    0x82A466A4,\n    0x82A466A4,\n    0x82A466A4,\n    0x82A466C4,\n    0x82A466C4,\n    0x82A466C4,\n    0x82A466C4,\n    0x82A466C4,\n    0x82A466D0,\n    0x82A466D0,\n    0x82A466D0,\n    0x82A466D0,\n    0x82A466D0,\n    0x82A466E0,\n    0x82A466E0,\n    0x82A466E0,\n    0x82A466E0,\n    0x82A466E0,\n    0x82A4659C,\n    0x82A43928,\n]\n\n[[switch]]\nbase = 0x82A5B97C\nr = 11\ndefault = 0x82A5C04C\nlabels = [\n    0x82A5BB84,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BAF8,\n    0x82A5BB00,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5B9A4,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BB98,\n    0x82A5BBAC,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BBCC,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BB34,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BBE0,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5B9A4,\n    0x82A5B9A4,\n    0x82A5B9DC,\n    0x82A5BE00,\n    0x82A5BA2C,\n    0x82A5BB1C,\n    0x82A5C04C,\n    0x82A5B9A4,\n    0x82A5C04C,\n    0x82A5BBF4,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5B9C0,\n    0x82A5C04C,\n    0x82A5BC08,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BCC4,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BCA8,\n    0x82A5BA34,\n    0x82A5BCF8,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BB6C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BD14,\n    0x82A5BD54,\n    0x82A5BC08,\n    0x82A5BD70,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BD88,\n    0x82A5C04C,\n    0x82A5BD9C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5C04C,\n    0x82A5BDB0,\n    0x82A5BDC4,\n    0x82A5C04C,\n    0x82A5BDD8,\n    0x82A5C04C,\n    0x82A5BDEC,\n    0x82A5C04C,\n    0x82A5BB54,\n    0x82A5BB54,\n    0x82A5B9A4,\n    0x82A5B9A4,\n    0x82A5B9A4,\n]\n\n[[switch]]\nbase = 0x82A5C52C\nr = 11\ndefault = 0x82A5CC40\nlabels = [\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5C90C,\n    0x82A5C928,\n    0x82A5C6E8,\n    0x82A5C730,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5C554,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC08,\n    0x82A5CA44,\n    0x82A5CC40,\n    0x82A5C568,\n    0x82A5C570,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5C63C,\n    0x82A5C9DC,\n    0x82A5C690,\n    0x82A5C948,\n    0x82A5C9C4,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5C7CC,\n    0x82A5CC40,\n    0x82A5C578,\n    0x82A5C5AC,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5C820,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CABC,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5C7A8,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC5C,\n    0x82A5CC40,\n    0x82A5CC40,\n    0x82A5CC80,\n]\n\n[[switch]]\nbase = 0x82A68188\nr = 10\ndefault = 0x82A6860C\nlabels = [\n    0x82A681B0,\n    0x82A68218,\n    0x82A682C0,\n    0x82A6828C,\n    0x82A682F0,\n    0x82A6832C,\n    0x82A683B4,\n    0x82A6843C,\n    0x82A6846C,\n    0x82A68498,\n    0x82A684C4,\n    0x82A684E0,\n    0x82A68574,\n    0x82A685A0,\n    0x82A685C0,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A6860C,\n    0x82A682F0,\n    0x82A6832C,\n    0x82A683B4,\n    0x82A6843C,\n]\n\n[[switch]]\nbase = 0x82AA2504\nr = 10\ndefault = 0x82AA67C4\nlabels = [\n    0x82AA67BC,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA5498,\n    0x82AA54F0,\n    0x82AA568C,\n    0x82AA67E4,\n    0x82AA53FC,\n    0x82AA53FC,\n    0x82AA53FC,\n    0x82AA62E8,\n    0x82AA67C4,\n    0x82AA67C4,\n    0x82AA67C4,\n    0x82AA67C4,\n    0x82AA67C4,\n    0x82AA67C4,\n    0x82AA67C4,\n    0x82AA4F44,\n    0x82AA4F18,\n    0x82AA51C8,\n    0x82AA5264,\n    0x82AA6404,\n    0x82AA67C4,\n    0x82AA66DC,\n    0x82AA6058,\n    0x82AA6748,\n    0x82AA6104,\n    0x82AA6768,\n    0x82AA67C4,\n    0x82AA252C,\n    0x82AA55F0,\n    0x82AA6518,\n    0x82AA5C70,\n    0x82AA5A10,\n    0x82AA55C0,\n    0x82AA5858,\n    0x82AA5698,\n    0x82AA5470,\n    0x82AA4F38,\n    0x82AA67E4,\n    0x82AA5520,\n    0x82AA67E4,\n    0x82AA50F8,\n    0x82AA5504,\n    0x82AA50BC,\n    0x82AA67E4,\n    0x82AA67A4,\n    0x82AA67C4,\n    0x82AA5644,\n    0x82AA5444,\n    0x82AA5444,\n    0x82AA5444,\n    0x82AA5444,\n    0x82AA5444,\n    0x82AA5444,\n    0x82AA5444,\n    0x82AA510C,\n    0x82AA67E4,\n    0x82AA4F00,\n    0x82AA512C,\n    0x82AA508C,\n    0x82AA508C,\n    0x82AA4F64,\n    0x82AA506C,\n    0x82AA5680,\n    0x82AA552C,\n    0x82AA552C,\n    0x82AA552C,\n    0x82AA552C,\n    0x82AA552C,\n    0x82AA552C,\n    0x82AA552C,\n    0x82AA5BDC,\n    0x82AA67C4,\n    0x82AA6668,\n]\n\n[[switch]]\nbase = 0x82AA6CE4\nr = 11\ndefault = 0x82AA7BEC\nlabels = [\n    0x82AA6D0C,\n    0x82AA7BEC,\n    0x82AA6E3C,\n    0x82AA7018,\n    0x82AA6EAC,\n    0x82AA73B4,\n    0x82AA7450,\n    0x82AA7018,\n    0x82AA767C,\n    0x82AA7750,\n]\n\n[[switch]]\nbase = 0x82AB2E3C\nr = 11\ndefault = 0x82AB33F8\nlabels = [\n    0x82AB3110,\n    0x82AB3110,\n    0x82AB2E64,\n    0x82AB31DC,\n    0x82AB2F60,\n    0x82AB2E7C,\n    0x82AB31BC,\n    0x82AB2EA4,\n    0x82AB33F8,\n    0x82AB3070,\n    0x82AB3110,\n    0x82AB3110,\n    0x82AB2FC8,\n    0x82AB303C,\n    0x82AB3110,\n    0x82AB33F8,\n    0x82AB33F8,\n    0x82AB33F8,\n    0x82AB33F8,\n    0x82AB33F8,\n    0x82AB2FB4,\n    0x82AB31FC,\n    0x82AB3110,\n    0x82AB3054,\n]\n\n[[switch]]\nbase = 0x82ABDF34\nr = 11\ndefault = 0x82ABE464\nlabels = [\n    0x82ABE538,\n    0x82ABE244,\n    0x82ABE244,\n    0x82ABE244,\n    0x82ABE244,\n    0x82ABE1F0,\n    0x82ABE200,\n    0x82ABE21C,\n    0x82ABE464,\n    0x82ABE2E8,\n    0x82ABE320,\n    0x82ABE364,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE054,\n    0x82ABE054,\n    0x82ABE054,\n    0x82ABDF5C,\n    0x82ABDF8C,\n    0x82ABDFB0,\n    0x82ABDFBC,\n    0x82ABDFC4,\n    0x82ABDFCC,\n    0x82ABDFD8,\n    0x82ABE008,\n    0x82ABE024,\n    0x82ABE030,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE098,\n    0x82ABE098,\n    0x82ABE098,\n    0x82ABE0DC,\n    0x82ABE0DC,\n    0x82ABE0DC,\n    0x82ABE154,\n    0x82ABE154,\n    0x82ABE154,\n    0x82ABE188,\n    0x82ABE188,\n    0x82ABE188,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE520,\n    0x82ABE4C8,\n    0x82ABE4D0,\n    0x82ABE4E4,\n    0x82ABE500,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE464,\n    0x82ABE3B8,\n    0x82ABE3B8,\n    0x82ABE3B8,\n    0x82ABE3B8,\n    0x82ABE3D4,\n    0x82ABE3D4,\n    0x82ABE3D4,\n    0x82ABE3D4,\n    0x82ABE3FC,\n    0x82ABE3FC,\n    0x82ABE3FC,\n    0x82ABE3FC,\n]\n\n[[switch]]\nbase = 0x82ABE6BC\nr = 11\ndefault = 0x82ABECD8\nlabels = [\n    0x82ABEDAC,\n    0x82ABEA88,\n    0x82ABEA88,\n    0x82ABEA88,\n    0x82ABEA88,\n    0x82ABEA34,\n    0x82ABEA44,\n    0x82ABEA60,\n    0x82ABECD8,\n    0x82ABEB5C,\n    0x82ABEB94,\n    0x82ABEBD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABE88C,\n    0x82ABE88C,\n    0x82ABE88C,\n    0x82ABE6E4,\n    0x82ABE710,\n    0x82ABE744,\n    0x82ABE758,\n    0x82ABE764,\n    0x82ABE770,\n    0x82ABE784,\n    0x82ABE7B8,\n    0x82ABE7E8,\n    0x82ABE810,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABE8D8,\n    0x82ABE8D8,\n    0x82ABE8D8,\n    0x82ABE918,\n    0x82ABE918,\n    0x82ABE918,\n    0x82ABE998,\n    0x82ABE998,\n    0x82ABE998,\n    0x82ABE9CC,\n    0x82ABE9CC,\n    0x82ABE9CC,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED94,\n    0x82ABED3C,\n    0x82ABED44,\n    0x82ABED58,\n    0x82ABED74,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABECD8,\n    0x82ABEC2C,\n    0x82ABEC2C,\n    0x82ABEC2C,\n    0x82ABEC2C,\n    0x82ABEC48,\n    0x82ABEC48,\n    0x82ABEC48,\n    0x82ABEC48,\n    0x82ABEC70,\n    0x82ABEC70,\n    0x82ABEC70,\n    0x82ABEC70,\n]\n\n[[switch]]\nbase = 0x82ABF210\nr = 11\ndefault = 0x82ABFE0C\nlabels = [\n    0x82ABFFD8,\n    0x82ABFB2C,\n    0x82ABFB2C,\n    0x82ABFB2C,\n    0x82ABFB2C,\n    0x82ABFAD8,\n    0x82ABFAE8,\n    0x82ABFB04,\n    0x82ABFE0C,\n    0x82ABFC30,\n    0x82ABFC84,\n    0x82ABFCE4,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABF5E8,\n    0x82ABF5E8,\n    0x82ABF5E8,\n    0x82ABF238,\n    0x82ABF27C,\n    0x82ABF2C8,\n    0x82ABF30C,\n    0x82ABF358,\n    0x82ABF39C,\n    0x82ABF3E8,\n    0x82ABF43C,\n    0x82ABF498,\n    0x82ABF4F4,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABF550,\n    0x82ABF550,\n    0x82ABF550,\n    0x82ABF584,\n    0x82ABF584,\n    0x82ABF584,\n    0x82ABF988,\n    0x82ABF988,\n    0x82ABF988,\n    0x82ABF8F4,\n    0x82ABF8F4,\n    0x82ABF8F4,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFECC,\n    0x82ABFE6C,\n    0x82ABFE74,\n    0x82ABFE88,\n    0x82ABFEA4,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFE0C,\n    0x82ABFD54,\n    0x82ABFD54,\n    0x82ABFD54,\n    0x82ABFD54,\n    0x82ABFD70,\n    0x82ABFD70,\n    0x82ABFD70,\n    0x82ABFD70,\n    0x82ABFD98,\n    0x82ABFD98,\n    0x82ABFD98,\n    0x82ABFD98,\n]\n\n[[switch]]\nbase = 0x82AC8C94\nr = 29\ndefault = 0x82AC9418\nlabels = [\n    0x82AC8CBC,\n    0x82AC8CF8,\n    0x82AC8D44,\n    0x82AC8D6C,\n    0x82AC8DF0,\n    0x82AC8E18,\n    0x82AC8E48,\n    0x82AC8E78,\n    0x82AC8F44,\n    0x82AC9000,\n    0x82AC9108,\n    0x82AC9130,\n    0x82AC92A4,\n    0x82AC9350,\n    0x82AC93C8,\n    0x82AC93F0,\n]\n\n"
  },
  {
    "path": "MarathonRecompLib/ppc/.gitignore",
    "content": "ppc_*"
  },
  {
    "path": "MarathonRecompLib/private/.gitignore",
    "content": "*\n!.gitignore"
  },
  {
    "path": "MarathonRecompLib/shader/.gitignore",
    "content": "*\n!shader_cache.h\n!.gitignore\n"
  },
  {
    "path": "MarathonRecompLib/shader/shader_cache.h",
    "content": "#pragma once\n\nstruct ShaderCacheEntry\n{\n    const uint64_t hash;\n    const uint32_t dxilOffset;\n    const uint32_t dxilSize;\n    const uint32_t spirvOffset;\n    const uint32_t spirvSize;\n    const uint32_t airOffset;\n    const uint32_t airSize;\n    const uint32_t specConstantsMask;\n    char filename[256];\n    struct GuestShader* guestShader;\n};\n\nextern ShaderCacheEntry g_shaderCacheEntries[];\nextern const size_t g_shaderCacheEntryCount;\n\nextern const uint8_t g_compressedDxilCache[];\nextern const size_t g_dxilCacheCompressedSize;\nextern const size_t g_dxilCacheDecompressedSize;\n\nextern const uint8_t g_compressedAirCache[];\nextern const size_t g_airCacheCompressedSize;\nextern const size_t g_airCacheDecompressedSize;\n\nextern const uint8_t g_compressedSpirvCache[];\nextern const size_t g_spirvCacheCompressedSize;\nextern const size_t g_spirvCacheDecompressedSize;\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n    <img src=\"https://raw.githubusercontent.com/IsaacMarovitz/MarathonRecompResources/refs/heads/main/images/logo/Logo.png\" width=\"512\"/>\n</p>\n\n---\n\n> [!CAUTION]\n> This recompilation is still under active development and is NOT meant for public use. Support will not be provided until an official release.\n\nMarathon Recompiled is an unofficial PC port of the Xbox 360 version of Sonic the Hedgehog (2006) created through the process of static recompilation. The port offers Windows, Linux, and macOS support.\n\n**This project does not include any game assets. You must provide the files from your own legally acquired copy of the game to install or build Marathon Recompiled.**\n\n[XenonRecomp](https://github.com/sonicnext-dev/XenonRecomp) and [XenosRecomp](https://github.com/sonicnext-dev/XenosRecomp) are the main recompilers used for converting the game's original PowerPC code and Xenos shaders into compatible C++ and HLSL code respectively. The development of these recompilers was directly inspired by [N64: Recompiled](https://github.com/N64Recomp/N64Recomp), which was used to create [Zelda 64: Recompiled](https://github.com/Zelda64Recomp/Zelda64Recomp).\n\n## Table of Contents\n\n- [Known Issues](#known-issues)\n- [FAQ](#faq)\n- [Building](#building)\n- [Credits](#credits)\n\n## Known Issues\n\nBefore reporting any issues, check if they are listed [here](https://github.com/sonicnext-dev/MarathonRecomp/issues).\n\n### Original Game Bugs\n\nGame bugs present on the original hardware are intentionally preserved and will not be fixed apart from a few minor exceptions in [#44](https://github.com/sonicnext-dev/MarathonRecomp/issues/44). Please do not report issues for these bugs and verify that the issue does not occur on original hardware before reporting. Bug reports for issues found in the original game will be rejected. Bugs that only happen in Marathon Recompiled must be accompanied by footage captured on original Xbox 360 hardware showing that the bug does not happen there.\n\n### File Picker Unavailable on Steam Deck in Game Mode\n\nDue to some restrictions of how the desktop environment on the Steam Deck works whilst in Game Mode, please note that you may need to at least first boot into Desktop Mode to be able to use the file picker to provide the game files.\n\nSimply booting at least once in Desktop Mode will enable the Deck to use the file picker when going back to Game Mode. You can complete the entire installation process while in Desktop Mode to save yourself the trouble of browsing through Game Mode if necessary.\n\n## FAQ\n\n### Do you have a website?\n\nMarathon Recompiled does not have an official website.\n\n**Please link here when directing anyone to the project.**\n\n> [!CAUTION]\n> Do not download builds of Marathon Recompiled from anywhere but our [Releases](https://github.com/sonicnext-dev/MarathonRecomp/releases/latest) page.\n>\n> **We will never distribute builds on other websites, via Discord servers or via third-party update tools.**\n\n### Why does the installer say my files are invalid?\n\nThe installer may display this error for several reasons. Please check the following to ensure your files are valid:\n\n- Please read the [How to Install](#how-to-install) section and make sure you've acquired all of the necessary files correctly.\n\n- Verify that you're not trying to add compressed files such as `.zip`, `.7z`, `.rar` or other formats.\n\n- Only use the **Add Folder** option if you're sure you have a directory with the content's files already extracted, which means it'll only contain files like `.xex`, `.ar.00`, `.arl` and others. **This option will not scan your folder for compatible content**.\n\n- Ensure that the files you've acquired correspond to the same region. **Discs and Title Updates from different regions can't be used together** and will fail to generate a patch.\n\n- The installer will only accept **original and unmodified files**. Do not attempt to provide modified files to the installer.\n\n### What are the keyboard bindings?\n\nPad|Key\n-|-\nA (Cross)|S\nB (Circle)|D\nX (Square)|A\nY (Triangle)|W\nD-Pad - Up|Unbound\nD-Pad - Down|Unbound\nD-Pad - Left|Q\nD-Pad - Right|E\nStart|Return\nBack (Select)|Backspace\nLeft Trigger (L2)|1\nRight Trigger (R2)|3\nLeft Bumper (L1)|Unbound\nRight Bumper (R1)|Unbound\nLeft Stick - Up|Up Arrow\nLeft Stick - Down|Down Arrow\nLeft Stick - Left|Left Arrow\nLeft Stick - Right|Right Arrow\nRight Stick - Up|Unbound\nRight Stick - Down|Unbound\nRight Stick - Left|Unbound\nRight Stick - Right|Unbound\n\n---\n\nYou can change the keyboard bindings by editing `config.toml` located in the [configuration directory](#where-is-the-save-data-and-configuration-file-stored), although using a controller is highly recommended until Action Remapping is added in a future update.\n\nRefer to the left column of [this enum template](https://github.com/sonicnext-dev/MarathonRecomp/blob/main/MarathonRecomp/user/config.cpp#L40) for a list of valid keys.\n\n*The default keyboard layout is based on Devil's Details' keyboard layout for Sonic Generations (2011)*.\n\n### Where is the save data and configuration file stored?\n\nThe save data and configuration files are stored at the following locations:\n\n- Windows: `%APPDATA%\\MarathonRecomp\\`\n- Linux: `~/.config/MarathonRecomp/`\n- macOS: `~/Library/Application Support/MarathonRecomp/`\n\nYou will find the save data under the `save` folder. The configuration file is named `config.toml`.\n\n### I want to update the game. How can I avoid losing my save data? Do I need to reinstall the game?\n\nUpdating the game can be done by simply copying and replacing the files from a [release](https://github.com/sonicnext-dev/MarathonRecomp/releases) on top of your existing installation. **Your save data and configuration will not be lost.** You won't need to reinstall the game, as the game files will always remain the same across versions of Marathon Recompiled.\n\n### How can I force the game to store the save data and configuration in the installation folder?\n\nYou can make the game ignore the [default configuration paths](#where-is-the-save-data-and-configuration-file-stored) and force it to save everything in the installation directory by creating an empty `portable.txt` file. You are directly responsible for the safekeeping of your save data and configuration if you choose this option.\n\n### How can I force the game to run the installation again?\n\nWhile it's unlikely you'll need to do this unless you've modified your game files by accident, you can force the installer to run again by using the launch argument: `--install`.\n\n### How can I force the game to run under X11 or Wayland?\n\nUse either of the following arguments to force SDL to run under the video driver you want:\n\n- X11: `--sdl-video-driver x11`\n- Wayland: `--sdl-video-driver wayland`\n\nThe second argument will be passed directly to SDL as a hint to try to initialize the game with your preferred option.\n\n### Where is the game data for the Flatpak version installed?\n\nGiven it is not possible to run the game where the Flatpak is stored, the game data will be installed to `~/.var/app/io.github.sonicnext_dev.marathonrecomp/data`. The Flatpak build will only recognize this directory as valid. Feel free to reuse this data directory with a native Linux build if you wish to switch in the future.\n\nIf you wish to move this data to another location, you can do so by creating a symlink from this directory to the one where you'll migrate your installation to.\n\n> [!WARNING]\n> Using external frame rate limiters or performance overlays may degrade performance or have negative consequences.\n\n### Can I install the game with a PlayStation 3 copy?\n\n**You cannot use the files from the PlayStation 3 version of the game.** Supporting these files would require an entirely new recompilation, as they have proprietary formatting that only works on PS3 and the code for these formats is only present in that version. All significant differences present in the PS3 version of the game have been included in this project as options.\n\n### Why is the game detecting my PlayStation controller as an Xbox controller?\n\nIf you're using a third-party input translation layer (such as DS4Windows or Steam Input), it is recommended that you disable these for full controller support.\n\n### What other platforms will be supported?\n\nThis project does not plan to support any more platforms other than Windows, Linux and macOS at the moment. Any contributors who wish to support more platforms should do so through a fork.\n\n## Building\n\n[Check out the building instructions here](/docs/BUILDING.md).\n\n## Credits\n\n### Marathon Recompiled\n- [ga2mer](https://github.com/ga2mer): Creator of the recompilation, laying the initial foundation for the project. Other responsibilities include maintaining the audio backend and providing various patches for the game.\n\n- [IsaacMarovitz](https://github.com/IsaacMarovitz): Graphics Programmer for the recompilation. Other responsibilities include maintaining macOS support and aiding in porting mod manager patches to the recompilation.\n\n- [squidbus](https://github.com/squidbus): Graphics Programmer for the recompilation. Aided in researching the game's internals.\n\n- [Hyper](https://github.com/hyperbx): Developer of system level features, such as achievement support and the custom menus, alongside various other patches and features to make the game feel right at home on modern systems. Aided in researching the game's internals.\n\n- [Rei-san](https://github.com/ReimousTH): Developer of quality of life patches and extensive amounts of research into the game's internals.\n\n- [Desko](https://github.com/FateForWindows): Aided in researching the game's internals and created many quality of life patches for the original game used by the recompilation.\n\n- [LJSTAR](https://github.com/LJSTARbird): Artist behind the project logo. Provided French localization for the custom menus.\n\n- [brianuuuSonic](https://github.com/brianuuu): Provided Japanese localization for the custom menus.\n\n- [Kitzuku](https://github.com/Kitzuku): Provided German localization for the options menu.\n\n- Ray Vassos: Provided German localization for the achievements menu.\n\n- [DaGuAr](https://x.com/TheDaguar): Provided Spanish localization for the custom menus.\n\n- [NextinHKRY](https://github.com/NextinMono): Provided Italian localization for the custom menus.\n\n- [Hotline Sehwani](https://www.youtube.com/channel/UC9NBX5UPq4fYvbr7bzvRvOg) & SilverIceSound: Produced the [installer music](https://www.youtube.com/watch?v=8mfOSTcTQNs) ([original prod.](https://www.youtube.com/watch?v=k_mGNwrxR5M) by [Tomoya Ohtani](https://www.youtube.com/@TomoyaOhtaniChannel)).\n\n### Special Thanks\n- Unleashed Recompiled Development Team: Created much of the ground work that made all of this possible and sped up development time considerably.\n\n- [Skyth](https://github.com/blueskythlikesclouds): Provided graphics consultation and support for dynamic aspect ratio.\n\n- [Darío](https://github.com/DarioSamo): Creator of the graphics hardware abstraction layer [plume](https://github.com/renderbag/plume), used by the project's graphics backend.\n\n- [Syko](https://x.com/UltraSyko): Aided in identifying fonts used by the original SonicNext logo.\n\n- [ocornut](https://github.com/ocornut): Creator of [Dear ImGui](https://github.com/ocornut/imgui), which is used as the backbone of the custom menus.\n\n- Raymond Chen: Useful resources on Windows application development with his blog [\"The Old New Thing\"](https://devblogs.microsoft.com/oldnewthing/).\n"
  },
  {
    "path": "docs/BUILDING.md",
    "content": "# Building\n\n## 1. Clone the Repository\n\nClone **MarathonRecomp** with submodules using [Git](https://git-scm.com/).\n```\ngit clone --recurse-submodules https://github.com/sonicnext-dev/MarathonRecomp.git\n```\n\n### Windows\nIf you skipped the `--recurse-submodules` argument during cloning, you can run `update_submodules.bat` to ensure the submodules are pulled.\n\n## 2. Add the Required Game Files\n\nCopy the following files from the game and place them inside `./MarathonRecompLib/private/`:\n- `default.xex`\n- `shader.arc`\n- `shader_lt.arc`\n\n`default.xex` is located in the game's root directory, while the others are located in `/xenon/archives`.\n\n[//]: # (> [!TIP])\n[//]: # (> It is recommended that you install the game using [an existing Marathon Recompiled release]&#40;https://github.com/sonicnext-dev/MarathonRecomp/releases/latest&#41; to acquire these files, otherwise you'll need to rely on third-party tools to extract them.)\n[//]: # (>)\n[//]: # (> Using the Marathon Recompiled installation wizard will also ensure that these files are compatible with each other so that they can be used with the build environment.)\n[//]: # (>)\n[//]: # (> When sourcing these files from an Marathon Recompiled installation, they will be stored under `game` and `update` subdirectories.)\n\n## 3. Install Dependencies\n\n### Windows\nYou will need to install [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/).\n\nIn the installer, you must select the following **Workloads** and **Individual components** for installation:\n- Desktop development with C++\n- C++ Clang Compiler for Windows\n- C++ CMake tools for Windows\n\n### Linux\nThe following command will install the required dependencies on a distro that uses `apt` (such as Debian-based distros).\n```bash\nsudo apt install autoconf automake libtool pkg-config curl cmake ninja-build clang clang-tools libgtk-3-dev\n```\nThe following command will install the required dependencies on a distro that uses `pacman` (such as Arch-based distros).\n```bash\nsudo pacman -S base-devel ninja lld clang gtk3\n```\nYou can also find the equivalent packages for your preferred distro.\n\n> [!NOTE]\n> This list may not be comprehensive for your particular distro and you may be required to install additional packages, should an error occur during configuration.\n\n### macOS\nYou will need to install the latest Xcode from Apple.\n\nThe following commands will install additional required dependencies, depending on which package manager you use.\n\nIf you use Homebrew:\n```bash\nbrew install cmake ninja pkg-config\n```\n\nIf you use MacPorts:\n```bash\nsudo port install cmake ninja pkg-config\n```\n\n## 4. Build the Project\n\n### Windows\n1. Open the repository directory in Visual Studio and wait for CMake generation to complete. If you don't plan to debug, switch to the `Release` configuration.\n\n> [!TIP]\n> If you need a Release-performant build and want to iterate on development without debugging, **it is highly recommended** that you use the `RelWithDebInfo` configuration for faster compile times.\n\n2. Under **Solution Explorer**, right-click and choose **Switch to CMake Targets View**.\n3. Right-click the **MarathonRecomp** project and choose **Set as Startup Item**, then choose **Add Debug Configuration**.\n4. Add a `currentDir` property to the first element under `configurations` in the generated JSON and set its value to the path to your game directory (where root is the directory containing `dlc`, `game`, etc).\n5. Start **MarathonRecomp**. The initial compilation may take a while to complete due to code and shader recompilation.\n\n### Linux\n1. Configure the project using CMake by navigating to the repository and running the following command.\n```bash\ncmake . --preset linux-release\n```\n\n> [!NOTE]\n> The available presets are `linux-debug`, `linux-relwithdebinfo` and `linux-release`.\n\n2. Build the project using the selected configuration.\n```bash\ncmake --build ./out/build/linux-release --target MarathonRecomp\n```\n\n3. Navigate to the directory that was specified as the output in the previous step and run the game.\n```bash\n./MarathonRecomp\n```\n\n### macOS\n1. Configure the project using CMake by navigating to the repository and running the following command.\n```bash\ncmake . --preset macos-release\n```\n\n> [!NOTE]\n> The available presets are `macos-debug`, `macos-relwithdebinfo` and `macos-release`.\n\n2. Build the project using the selected configuration.\n```bash\ncmake --build ./out/build/macos-release --target MarathonRecomp\n```\n\n3. Navigate to the directory that was specified as the output in the previous step and run the game.\n```bash\nopen -a MarathonRecomp.app\n```\n"
  },
  {
    "path": "docs/DUMPING-en.md",
    "content": "# Dumping\n\n### Pre-requisites\n- Xbox 360 (modifications not necessary)\n- Xbox 360 Hard Drive (20 GB minimum)\n- Xbox 360 Hard Drive Transfer Cable (or a compatible SATA to USB adapter)\n- Sonic the Hedgehog (2006) for Xbox 360\n    - Retail Disc or Digital Copy.\n    - All available DLC are optional.\n- [7-Zip](https://7-zip.org/download.html) (for extracting Velocity)\n- [Velocity](https://github.com/Gualdimar/Velocity/releases/download/xex%2Biso-branch/Velocity-XEXISO.rar) (Gualdimar's fork)\n\n> [!TIP]\n> If you do not have the Xbox 360 Hard Drive Transfer Cable, please ensure that you purchase the correct revision of it for your console.\n>\n> The latest revision works with both original Xbox 360 and Xbox 360 S|E hard drives, but the first revision only works with original Xbox 360 hard drives.\n>\n> To know which is which, the first revision cable is gray, whereas the latest revision (which supports any Xbox 360 hard drive) is black.\n\n### Instructions\n\n> [!NOTE]\n> If you have a digital copy of Sonic the Hedgehog, skip to step 4.\n\n1. Insert your retail disc copy of Sonic the Hedgehog into the Xbox 360 disc tray.\n2. At the Xbox Dashboard, go over to the disc tile under the **home** tab and press X to view **Game Details**.\n3. Under the **overview** tab, select the **Install** tile and choose to install to the primary hard drive.\n4. Once installed, turn off your Xbox 360 and remove the hard drive from your console.\n\n> [!TIP]\n> You may consult the following guides if you're unsure on how to do this:\n> - [Xbox 360](https://www.ifixit.com/Guide/Xbox+360+Hard+Drive+Replacement/3326)\n> - [Xbox 360 S](https://www.ifixit.com/Guide/Xbox+360+S+Hard+Drive+Replacement/3184)\n> - [Xbox 360 E](https://www.ifixit.com/Guide/Xbox+360+E+Hard+Drive+Replacement/22179)\n\n5. Using the Xbox 360 Hard Drive Transfer Cable (or compatible SATA to USB adapter), connect your Xbox 360 hard drive to your PC.\n\n> [!CAUTION]\n> If you're using an unofficial SATA to USB adapter, you may need to remove the hard drive from its enclosure in order to connect it.\n>\n> For original Xbox 360 hard drives, this process is as simple as [removing some screws and cracking open the enclosure](https://www.ifixit.com/Guide/Xbox+360+HDD+Replacement/3430).\n>\n> For Xbox 360 S|E hard drives, this enclosure is glued shut and removing the hard drive may be an irreversible process!\n>\n> **It is highly recommended** that you obtain the official Xbox 360 Hard Drive Transfer Cable in order to proceed.\n\n6. Download [the latest release of Velocity](https://github.com/Gualdimar/Velocity/releases/download/xex%2Biso-branch/Velocity-XEXISO.rar) and open the `*.rar` file using [7-Zip](https://7-zip.org/download.html), then extract its contents anywhere that's convenient to you.\n7. Create a new folder anywhere that's convenient to you for storing the game files.\n\n> [!NOTE]\n> If you're using Linux, skip to step 9.\n\n8. Right-click `Velocity.exe` and click **Properties**, then under the **Compatibility** tab, tick **Run this program as an administrator** and click **OK**. This is required in order for the program to recognize the hard drive. You can now launch `Velocity.exe`.\n9. You should see a **Device Detected** message appear on launch asking if you would like to open the **Device Content Viewer**. Click **Yes**.\n10. You should now see a tree view of your hard drive's contents. Expand the tree nodes for `/Shared Items/Games/` (and optionally `/Shared Items/DLC/`, if you have the DLC installed).\n11. Hold the CTRL key and click on **SONIC THE HEDGEHOG** under the `Games` node, as well as the **DLC** under the `DLC` node, if you have the DLC installed. Ensure all are selected before the next step.\n12. Right-click any of the selected items and click **Copy Selected to Local Disk**, then navigate to the folder you created in step 7 and select it. Velocity will now begin copying the game files to your PC.\n13. Once the transfer is complete, close the **Device Content Viewer** window and navigate to **Tools > Device Tools > Raw Device Viewer**.\n14. Once the transfer is complete, you should now have all of the necessary files for installation. [Return to the readme and proceed to the next step](/README.md#how-to-install)."
  },
  {
    "path": "flatpak/README.md",
    "content": "Build\n```sh\nflatpak-builder --force-clean --user --install-deps-from=flathub --repo=repo --install builddir io.github.sonicnext_dev.marathonrecomp.json\n```\n\nBundle\n```sh\nflatpak build-bundle repo io.github.sonicnext_dev.marathonrecomp.flatpak io.github.sonicnext_dev.marathonrecomp --runtime-repo=https://flathub.org/repo/flathub.flatpakrepo\n```\n\n"
  },
  {
    "path": "flatpak/io.github.sonicnext_dev.marathonrecomp.desktop",
    "content": "[Desktop Entry]\nName=Marathon Recompiled\nExec=/app/bin/MarathonRecomp\nType=Application\nIcon=io.github.sonicnext_dev.marathonrecomp\nCategories=Game;\nComment=Static recompilation of Sonic the Hedgehog (2006).\nMimeType=x-scheme-handler/marathonrecomp\n"
  },
  {
    "path": "flatpak/io.github.sonicnext_dev.marathonrecomp.json",
    "content": "{\n  \"id\": \"io.github.sonicnext_dev.marathonrecomp\",\n  \"runtime\": \"org.freedesktop.Platform\",\n  \"runtime-version\": \"23.08\",\n  \"sdk\": \"org.freedesktop.Sdk\",\n  \"sdk-extensions\" : [ \"org.freedesktop.Sdk.Extension.llvm18\" ],\n  \"finish-args\": [\n    \"--share=network\",\n    \"--socket=wayland\",\n    \"--socket=fallback-x11\",\n    \"--socket=pulseaudio\",\n    \"--device=all\",\n    \"--talk-name=org.mpris.MediaPlayer2.*\",\n    \"--filesystem=host\",\n    \"--filesystem=/media\",\n    \"--filesystem=/run/media\",\n    \"--filesystem=/mnt\"\n  ],\n  \"modules\": [\n    {\n      \"name\": \"MarathonRecomp\",\n      \"buildsystem\": \"simple\",\n      \"build-commands\": [\n        \"cmake --preset linux-release -DMARATHON_RECOMP_FLATPAK=ON -DSDL2MIXER_VORBIS=VORBISFILE -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache\",\n        \"cmake --build out/build/linux-release --target MarathonRecomp\",\n        \"mkdir -p /app/bin\",\n        \"cp out/build/linux-release/MarathonRecomp/MarathonRecomp /app/bin/MarathonRecomp\",\n        \"install -Dm644 MarathonRecompResources/images/game_icon.png /app/share/icons/hicolor/128x128/apps/${FLATPAK_ID}.png\",\n        \"install -Dm644 flatpak/io.github.sonicnext_dev.marathonrecomp.metainfo.xml /app/share/metainfo/${FLATPAK_ID}.metainfo.xml\",\n        \"install -Dm644 flatpak/io.github.sonicnext_dev.marathonrecomp.desktop /app/share/applications/${FLATPAK_ID}.desktop\"\n      ],\n      \"sources\": [\n        {\n          \"type\": \"dir\",\n          \"path\": \"../\"\n        }\n      ],\n      \"build-options\": {\n        \"append-path\": \"/usr/lib/sdk/llvm18/bin\",\n        \"prepend-ld-library-path\": \"/usr/lib/sdk/llvm18/lib\",\n        \"build-args\": [\n          \"--share=network\"\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "flatpak/io.github.sonicnext_dev.marathonrecomp.metainfo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n  <id>io.github.sonicnext_dev.marathonrecomp</id>\n  \n  <name>Marathon Recompiled</name>\n  <summary>An unofficial PC port of Sonic the Hedgehog (2006).</summary>\n  \n  <metadata_license>CC0-1.0</metadata_license>\n  <project_license>GPL-3.0+</project_license>\n  \n  <supports>\n    <control>pointing</control>\n    <control>keyboard</control>\n    <control>touch</control>\n  </supports>\n  \n  <description>\n    <p>\n      An unofficial PC port of the Xbox 360 version of Sonic the Hedgehog (2006) created through the process of static recompilation.\n\n      https://github.com/sonicnext-dev/MarathonRecomp\n    </p>\n  </description>\n  \n  <launchable type=\"desktop-id\">io.github.sonicnext_dev.marathonrecomp.desktop</launchable>\n</component>\n"
  },
  {
    "path": "thirdparty/.gitignore",
    "content": "!*\r\n\r\n# Visual Studio 2015/2017 cache/options directory\r\n.vs/\r\n# The packages folder can be ignored because of Package Restore\r\n**/[Pp]ackages/*\r\n# except build/, which is used as an MSBuild target.\r\n!**/[Pp]ackages/build/\r\n"
  },
  {
    "path": "thirdparty/CMakeLists.txt",
    "content": "cmake_policy(SET CMP0077 NEW)\n\nset(MSDF_ATLAS_BUILD_STANDALONE OFF)\nset(MSDF_ATLAS_USE_SKIA OFF)\nset(MSDF_ATLAS_NO_ARTERY_FONT ON)\nset(MSDFGEN_DISABLE_PNG ON)\n\nset(SDL2MIXER_DEPS_SHARED OFF)\nset(SDL2MIXER_VENDORED ON)\nset(SDL2MIXER_FLAC OFF)\nset(SDL2MIXER_MOD OFF)\nset(SDL2MIXER_MP3 OFF)\nset(SDL2MIXER_MIDI OFF)\nset(SDL2MIXER_OPUS OFF)\nset(SDL2MIXER_VORBIS \"VORBISFILE\")\nset(SDL2MIXER_WAVPACK OFF)\n\nif (CMAKE_SYSTEM_NAME MATCHES \"Linux\")\n    set(PLUME_SDL_VULKAN_ENABLED ON CACHE BOOL \"\")\nendif()\n\nif (WIN32)\n    set(PLUME_D3D12_AGILITY_SDK_ENABLED ON CACHE BOOL \"\")\nendif()\n\nif (APPLE AND CMAKE_OSX_ARCHITECTURES)\n    set(BASE_ARCHITECTURE \"${CMAKE_OSX_ARCHITECTURES}\")\nelseif (CMAKE_SYSTEM_PROCESSOR)\n    set(BASE_ARCHITECTURE \"${CMAKE_SYSTEM_PROCESSOR}\")\nelse()\n    set(BASE_ARCHITECTURE \"${CMAKE_HOST_SYSTEM_PROCESSOR}\")\nendif()\n\n# Next, match common architecture strings down to a known common value.\nif (BASE_ARCHITECTURE MATCHES \"(x86)|(X86)|(amd64)|(AMD64)\")\n    set(ARCHITECTURE \"x86_64\")\nelseif (BASE_ARCHITECTURE MATCHES \"(aarch64)|(AARCH64)|(arm64)|(ARM64)\")\n    set(ARCHITECTURE \"arm64\")\nelse()\n    message(FATAL_ERROR \"Unsupported CPU architecture: ${BASE_ARCHITECTURE}\")\nendif()\n\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/msdf-atlas-gen\")\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended\")\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/o1heap\")\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/SDL\")\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/SDL_mixer\")\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/plume\")\nadd_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/ffmpeg-core\")\n\nif (APPLE)\n    add_subdirectory(\"${MARATHON_RECOMP_THIRDPARTY_ROOT}/MoltenVK\")\nendif()\n"
  },
  {
    "path": "thirdparty/MoltenVK/CMakeLists.txt",
    "content": "# Simple CMake script to compile MoltenVK with SPIRV-Cross as a shared library target\n\n# Prepare MoltenVK Git revision\nfind_package(Git)\nif(GIT_FOUND)\n    execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD\n            OUTPUT_VARIABLE MVK_GIT_REV\n            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK\n            ERROR_QUIET\n            OUTPUT_STRIP_TRAILING_WHITESPACE)\nendif()\nset(MVK_GENERATED_INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/Generated)\nfile(WRITE ${MVK_GENERATED_INCLUDES}/mvkGitRevDerived.h \"static const char* mvkRevString = \\\"${MVK_GIT_REV}\\\";\")\nmessage(STATUS \"MoltenVK revision: ${MVK_GIT_REV}\")\n\n# Prepare MoltenVK version\nfile(READ ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK/MoltenVK/API/mvk_private_api.h MVK_PRIVATE_API)\nstring(REGEX MATCH \"#define MVK_VERSION_MAJOR   [0-9]+\" MVK_VERSION_MAJOR_LINE \"${MVK_PRIVATE_API}\")\nstring(REGEX MATCH \"[0-9]+\" MVK_VERSION_MAJOR \"${MVK_VERSION_MAJOR_LINE}\")\nstring(REGEX MATCH \"#define MVK_VERSION_MINOR   [0-9]+\" MVK_VERSION_MINOR_LINE \"${MVK_PRIVATE_API}\")\nstring(REGEX MATCH \"[0-9]+\" MVK_VERSION_MINOR \"${MVK_VERSION_MINOR_LINE}\")\nstring(REGEX MATCH \"#define MVK_VERSION_PATCH   [0-9]+\" MVK_VERSION_PATCH_LINE \"${MVK_PRIVATE_API}\")\nstring(REGEX MATCH \"[0-9]+\" MVK_VERSION_PATCH \"${MVK_VERSION_PATCH_LINE}\")\nset(MVK_VERSION \"${MVK_VERSION_MAJOR}.${MVK_VERSION_MINOR}.${MVK_VERSION_PATCH}\")\nmessage(STATUS \"MoltenVK version: ${MVK_VERSION}\")\n\n# Find required system libraries\nfind_library(APPKIT_LIBRARY AppKit REQUIRED)\nfind_library(FOUNDATION_LIBRARY Foundation REQUIRED)\nfind_library(IOKIT_LIBRARY IOKit REQUIRED)\nfind_library(IOSURFACE_LIBRARY IOSurface REQUIRED)\nfind_library(METAL_LIBRARY Metal REQUIRED)\nfind_library(QUARTZCORE_LIBRARY QuartzCore REQUIRED)\n\n# SPIRV-Cross\noption(SPIRV_CROSS_CLI \"\" OFF)\noption(SPIRV_CROSS_ENABLE_TESTS \"\" OFF)\noption(SPIRV_CROSS_ENABLE_HLSL \"\" OFF)\noption(SPIRV_CROSS_ENABLE_CPP \"\" OFF)\noption(SPIRV_CROSS_SKIP_INSTALL \"\" ON)\nadd_subdirectory(SPIRV-Cross)\n\n# Common\nset(MVK_COMMON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/Common)\nfile(GLOB_RECURSE MVK_COMMON_SOURCES CONFIGURE_DEPENDS\n    ${MVK_COMMON_DIR}/*.cpp\n    ${MVK_COMMON_DIR}/*.m\n    ${MVK_COMMON_DIR}/*.mm)\nset(MVK_COMMON_INCLUDES ${MVK_COMMON_DIR})\n\nadd_library(MoltenVKCommon STATIC ${MVK_COMMON_SOURCES})\ntarget_include_directories(MoltenVKCommon PUBLIC ${MVK_COMMON_INCLUDES})\ntarget_compile_options(MoltenVKCommon PRIVATE -w)\n\n# MoltenVKShaderConverter\nset(MVK_SHADER_CONVERTER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVKShaderConverter)\nfile(GLOB_RECURSE MVK_SHADER_CONVERTER_SOURCES CONFIGURE_DEPENDS\n    ${MVK_SHADER_CONVERTER_DIR}/MoltenVKShaderConverter/*.cpp\n    ${MVK_SHADER_CONVERTER_DIR}/MoltenVKShaderConverter/*.m\n    ${MVK_SHADER_CONVERTER_DIR}/MoltenVKShaderConverter/*.mm)\nset(MVK_SHADER_CONVERTER_INCLUDES ${MVK_SHADER_CONVERTER_DIR} ${MVK_SHADER_CONVERTER_DIR}/include)\n\nadd_library(MoltenVKShaderConverter STATIC ${MVK_SHADER_CONVERTER_SOURCES})\ntarget_include_directories(MoltenVKShaderConverter PUBLIC ${MVK_SHADER_CONVERTER_INCLUDES})\ntarget_compile_options(MoltenVKShaderConverter PRIVATE -w)\ntarget_link_libraries(MoltenVKShaderConverter PRIVATE spirv-cross-msl spirv-cross-reflect MoltenVKCommon)\ntarget_compile_definitions(MoltenVKShaderConverter PRIVATE MVK_EXCLUDE_SPIRV_TOOLS=1)\n\n# MoltenVK\nset(MVK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/MoltenVK)\nfile(GLOB_RECURSE MVK_SOURCES CONFIGURE_DEPENDS\n    ${MVK_DIR}/MoltenVK/*.cpp\n    ${MVK_DIR}/MoltenVK/*.m\n    ${MVK_DIR}/MoltenVK/*.mm)\nfile(GLOB MVK_SRC_INCLUDES LIST_DIRECTORIES ON ${MVK_DIR}/MoltenVK/*)\nset(MVK_INCLUDES\n    ${MVK_SRC_INCLUDES} ${MVK_GENERATED_INCLUDES} ${MVK_DIR}/include\n    ${MARATHON_RECOMP_THIRDPARTY_ROOT}/plume/contrib/Vulkan-Headers/include)\n\nadd_library(MoltenVK SHARED ${MVK_SOURCES})\ntarget_include_directories(MoltenVK PRIVATE ${MVK_INCLUDES})\ntarget_compile_options(MoltenVK PRIVATE -w)\ntarget_link_libraries(MoltenVK PRIVATE\n    ${APPKIT_LIBRARY} ${FOUNDATION_LIBRARY} ${IOKIT_LIBRARY} ${IOSURFACE_LIBRARY} ${METAL_LIBRARY} ${QUARTZCORE_LIBRARY}\n    spirv-cross-msl MoltenVKCommon MoltenVKShaderConverter)\ntarget_compile_definitions(MoltenVK PRIVATE MVK_FRAMEWORK_VERSION=${MVK_VERSION} MVK_USE_CEREAL=0)\n"
  },
  {
    "path": "thirdparty/o1heap/CMakeLists.txt",
    "content": "project(\"o1heap\")\r\n\r\nadd_library(o1heap \"o1heap.h\" \"o1heap.c\")\r\ntarget_include_directories(o1heap PUBLIC \".\")"
  },
  {
    "path": "thirdparty/o1heap/o1heap.c",
    "content": "// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\r\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\r\n// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,\r\n// and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions\r\n// of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\r\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\r\n// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\r\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n//\r\n// Copyright (c) 2020 Pavel Kirienko\r\n// Authors: Pavel Kirienko <pavel.kirienko@zubax.com>\r\n\r\n#include \"o1heap.h\"\r\n#include <assert.h>\r\n#include <limits.h>\r\n\r\n// ---------------------------------------- BUILD CONFIGURATION OPTIONS ----------------------------------------\r\n\r\n/// Define this macro to include build configuration header. This is an alternative to the -D compiler flag.\r\n/// Usage example with CMake: \"-DO1HEAP_CONFIG_HEADER=\\\"${CMAKE_CURRENT_SOURCE_DIR}/my_o1heap_config.h\\\"\"\r\n#ifdef O1HEAP_CONFIG_HEADER\r\n#    include O1HEAP_CONFIG_HEADER\r\n#endif\r\n\r\n/// The assertion macro defaults to the standard assert().\r\n/// It can be overridden to manually suppress assertion checks or use a different error handling policy.\r\n#ifndef O1HEAP_ASSERT\r\n// Intentional violation of MISRA: the assertion check macro cannot be replaced with a function definition.\r\n#    define O1HEAP_ASSERT(x) assert(x)  // NOSONAR\r\n#endif\r\n\r\n/// Allow usage of compiler intrinsics for branch annotation and CLZ.\r\n#ifndef O1HEAP_USE_INTRINSICS\r\n#    define O1HEAP_USE_INTRINSICS 1\r\n#endif\r\n\r\n/// Branch probability annotations are used to improve the worst case execution time (WCET). They are entirely optional.\r\n#if O1HEAP_USE_INTRINSICS && !defined(O1HEAP_LIKELY)\r\n#    if defined(__GNUC__) || defined(__clang__) || defined(__CC_ARM)\r\n// Intentional violation of MISRA: branch hinting macro cannot be replaced with a function definition.\r\n#        define O1HEAP_LIKELY(x) __builtin_expect((x), 1)  // NOSONAR\r\n#    endif\r\n#endif\r\n#ifndef O1HEAP_LIKELY\r\n#    define O1HEAP_LIKELY(x) x\r\n#endif\r\n\r\n/// This option is used for testing only. Do not use in production.\r\n#ifndef O1HEAP_PRIVATE\r\n#    define O1HEAP_PRIVATE static inline\r\n#endif\r\n\r\n/// Count leading zeros (CLZ) is used for fast computation of binary logarithm (which needs to be done very often).\r\n/// Most of the modern processors (including the embedded ones) implement dedicated hardware support for fast CLZ\r\n/// computation, which is available via compiler intrinsics. The default implementation will automatically use\r\n/// the intrinsics for some of the compilers; for others it will default to the slow software emulation,\r\n/// which can be overridden by the user via O1HEAP_CONFIG_HEADER. The library guarantees that the argument is positive.\r\n#if O1HEAP_USE_INTRINSICS && !defined(O1HEAP_CLZ)\r\n#    if defined(__GNUC__) || defined(__clang__) || defined(__CC_ARM)\r\n#        if SIZE_MAX == 0xFFFFFFFFFFFFFFFF\r\n#            define O1HEAP_CLZ __builtin_clzll\r\n#        else\r\n#            define O1HEAP_CLZ __builtin_clzl\r\n#        endif\r\n#    endif\r\n#endif\r\n#ifndef O1HEAP_CLZ\r\nO1HEAP_PRIVATE uint_fast8_t O1HEAP_CLZ(const size_t x)\r\n{\r\n    O1HEAP_ASSERT(x > 0);\r\n    size_t       t = ((size_t)1U) << ((sizeof(size_t) * CHAR_BIT) - 1U);\r\n    uint_fast8_t r = 0;\r\n    while ((x & t) == 0)\r\n    {\r\n        t >>= 1U;\r\n        r++;\r\n    }\r\n    return r;\r\n}\r\n#endif\r\n\r\n// ---------------------------------------- INTERNAL DEFINITIONS ----------------------------------------\r\n\r\n#if __STDC_VERSION__ < 201112L\r\n// Intentional violation of MISRA: static assertion macro cannot be replaced with a function definition.\r\n#    define static_assert(x, ...) typedef char _static_assert_gl(_static_assertion_, __LINE__)[(x) ? 1 : -1]  // NOSONAR\r\n#    define _static_assert_gl(a, b) _static_assert_gl_impl(a, b)                                              // NOSONAR\r\n// Intentional violation of MISRA: the paste operator ## cannot be avoided in this context.\r\n#    define _static_assert_gl_impl(a, b) a##b  // NOSONAR\r\n#endif\r\n\r\n/// The overhead is at most O1HEAP_ALIGNMENT bytes large,\r\n/// then follows the user data which shall keep the next fragment aligned.\r\n#define FRAGMENT_SIZE_MIN (O1HEAP_ALIGNMENT * 2U)\r\n\r\n/// This is risky, handle with care: if the allocation amount plus per-fragment overhead exceeds 2**(b-1),\r\n/// where b is the pointer bit width, then ceil(log2(amount)) yields b; then 2**b causes an integer overflow.\r\n/// To avoid this, we put a hard limit on fragment size (which is amount + per-fragment overhead): 2**(b-1)\r\n#define FRAGMENT_SIZE_MAX ((SIZE_MAX >> 1U) + 1U)\r\n\r\n/// Normally we should subtract log2(FRAGMENT_SIZE_MIN) but log2 is bulky to compute using the preprocessor only.\r\n/// We will certainly end up with unused bins this way, but it is cheap to ignore.\r\n#define NUM_BINS_MAX (sizeof(size_t) * CHAR_BIT)\r\n\r\nstatic_assert((O1HEAP_ALIGNMENT& (O1HEAP_ALIGNMENT - 1U)) == 0U, \"Not a power of 2\");\r\nstatic_assert((FRAGMENT_SIZE_MIN& (FRAGMENT_SIZE_MIN - 1U)) == 0U, \"Not a power of 2\");\r\nstatic_assert((FRAGMENT_SIZE_MAX& (FRAGMENT_SIZE_MAX - 1U)) == 0U, \"Not a power of 2\");\r\n\r\ntypedef struct Fragment Fragment;\r\n\r\ntypedef struct FragmentHeader\r\n{\r\n    Fragment* next;\r\n    Fragment* prev;\r\n    size_t    size;\r\n    bool      used;\r\n} FragmentHeader;\r\nstatic_assert(sizeof(FragmentHeader) <= O1HEAP_ALIGNMENT, \"Memory layout error\");\r\n\r\nstruct Fragment\r\n{\r\n    FragmentHeader header;\r\n    // Everything past the header may spill over into the allocatable space. The header survives across alloc/free.\r\n    Fragment* next_free;  // Next free fragment in the bin; NULL in the last one.\r\n    Fragment* prev_free;  // Same but points back; NULL in the first one.\r\n};\r\nstatic_assert(sizeof(Fragment) <= FRAGMENT_SIZE_MIN, \"Memory layout error\");\r\n\r\nstruct O1HeapInstance\r\n{\r\n    Fragment* bins[NUM_BINS_MAX];  ///< Smallest fragments are in the bin at index 0.\r\n    size_t    nonempty_bin_mask;   ///< Bit 1 represents a non-empty bin; bin at index 0 is for the smallest fragments.\r\n\r\n    O1HeapDiagnostics diagnostics;\r\n};\r\n\r\n/// The amount of space allocated for the heap instance.\r\n/// Its size is padded up to O1HEAP_ALIGNMENT to ensure correct alignment of the allocation arena that follows.\r\n#define INSTANCE_SIZE_PADDED ((sizeof(O1HeapInstance) + O1HEAP_ALIGNMENT - 1U) & ~(O1HEAP_ALIGNMENT - 1U))\r\n\r\nstatic_assert(INSTANCE_SIZE_PADDED >= sizeof(O1HeapInstance), \"Invalid instance footprint computation\");\r\nstatic_assert((INSTANCE_SIZE_PADDED% O1HEAP_ALIGNMENT) == 0U, \"Invalid instance footprint computation\");\r\n\r\n/// Undefined for zero argument.\r\nO1HEAP_PRIVATE uint_fast8_t log2Floor(const size_t x)\r\n{\r\n    O1HEAP_ASSERT(x > 0);\r\n    // NOLINTNEXTLINE redundant cast to the same type.\r\n    return (uint_fast8_t)(((sizeof(x) * CHAR_BIT) - 1U) - ((uint_fast8_t)O1HEAP_CLZ(x)));\r\n}\r\n\r\n/// Special case: if the argument is zero, returns zero.\r\nO1HEAP_PRIVATE uint_fast8_t log2Ceil(const size_t x)\r\n{\r\n    // NOLINTNEXTLINE redundant cast to the same type.\r\n    return (x <= 1U) ? 0U : (uint_fast8_t)((sizeof(x) * CHAR_BIT) - ((uint_fast8_t)O1HEAP_CLZ(x - 1U)));\r\n}\r\n\r\n/// Raise 2 into the specified power.\r\n/// You might be tempted to do something like (1U << power). WRONG! We humans are prone to forgetting things.\r\n/// If you forget to cast your 1U to size_t or ULL, you may end up with undefined behavior.\r\nO1HEAP_PRIVATE size_t pow2(const uint_fast8_t power)\r\n{\r\n    return ((size_t)1U) << power;\r\n}\r\n\r\n/// This is equivalent to pow2(log2Ceil(x)). Undefined for x<2.\r\nO1HEAP_PRIVATE size_t roundUpToPowerOf2(const size_t x)\r\n{\r\n    O1HEAP_ASSERT(x >= 2U);\r\n    // NOLINTNEXTLINE redundant cast to the same type.\r\n    return ((size_t)1U) << ((sizeof(x) * CHAR_BIT) - ((uint_fast8_t)O1HEAP_CLZ(x - 1U)));\r\n}\r\n\r\n/// Links two fragments so that their next/prev pointers point to each other; left goes before right.\r\nO1HEAP_PRIVATE void interlink(Fragment* const left, Fragment* const right)\r\n{\r\n    if (O1HEAP_LIKELY(left != NULL))\r\n    {\r\n        left->header.next = right;\r\n    }\r\n    if (O1HEAP_LIKELY(right != NULL))\r\n    {\r\n        right->header.prev = left;\r\n    }\r\n}\r\n\r\n/// Adds a new fragment into the appropriate bin and updates the lookup mask.\r\nO1HEAP_PRIVATE void rebin(O1HeapInstance* const handle, Fragment* const fragment)\r\n{\r\n    O1HEAP_ASSERT(handle != NULL);\r\n    O1HEAP_ASSERT(fragment != NULL);\r\n    O1HEAP_ASSERT(fragment->header.size >= FRAGMENT_SIZE_MIN);\r\n    O1HEAP_ASSERT((fragment->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n    const uint_fast8_t idx = log2Floor(fragment->header.size / FRAGMENT_SIZE_MIN);  // Round DOWN when inserting.\r\n    O1HEAP_ASSERT(idx < NUM_BINS_MAX);\r\n    // Add the new fragment to the beginning of the bin list.\r\n    // I.e., each allocation will be returning the most-recently-used fragment -- good for caching.\r\n    fragment->next_free = handle->bins[idx];\r\n    fragment->prev_free = NULL;\r\n    if (O1HEAP_LIKELY(handle->bins[idx] != NULL))\r\n    {\r\n        handle->bins[idx]->prev_free = fragment;\r\n    }\r\n    handle->bins[idx] = fragment;\r\n    handle->nonempty_bin_mask |= pow2(idx);\r\n}\r\n\r\n/// Removes the specified fragment from its bin.\r\nO1HEAP_PRIVATE void unbin(O1HeapInstance* const handle, const Fragment* const fragment)\r\n{\r\n    O1HEAP_ASSERT(handle != NULL);\r\n    O1HEAP_ASSERT(fragment != NULL);\r\n    O1HEAP_ASSERT(fragment->header.size >= FRAGMENT_SIZE_MIN);\r\n    O1HEAP_ASSERT((fragment->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n    const uint_fast8_t idx = log2Floor(fragment->header.size / FRAGMENT_SIZE_MIN);  // Round DOWN when removing.\r\n    O1HEAP_ASSERT(idx < NUM_BINS_MAX);\r\n    // Remove the bin from the free fragment list.\r\n    if (O1HEAP_LIKELY(fragment->next_free != NULL))\r\n    {\r\n        fragment->next_free->prev_free = fragment->prev_free;\r\n    }\r\n    if (O1HEAP_LIKELY(fragment->prev_free != NULL))\r\n    {\r\n        fragment->prev_free->next_free = fragment->next_free;\r\n    }\r\n    // Update the bin header.\r\n    if (O1HEAP_LIKELY(handle->bins[idx] == fragment))\r\n    {\r\n        O1HEAP_ASSERT(fragment->prev_free == NULL);\r\n        handle->bins[idx] = fragment->next_free;\r\n        if (O1HEAP_LIKELY(handle->bins[idx] == NULL))\r\n        {\r\n            handle->nonempty_bin_mask &= ~pow2(idx);\r\n        }\r\n    }\r\n}\r\n\r\n// ---------------------------------------- PUBLIC API IMPLEMENTATION ----------------------------------------\r\n\r\nO1HeapInstance* o1heapInit(void* const base, const size_t size)\r\n{\r\n    O1HeapInstance* out = NULL;\r\n    if ((base != NULL) && ((((size_t)base) % O1HEAP_ALIGNMENT) == 0U) &&\r\n        (size >= (INSTANCE_SIZE_PADDED + FRAGMENT_SIZE_MIN)))\r\n    {\r\n        // Allocate the core heap metadata structure in the beginning of the arena.\r\n        O1HEAP_ASSERT(((size_t)base) % sizeof(O1HeapInstance*) == 0U);\r\n        out = (O1HeapInstance*)base;\r\n        out->nonempty_bin_mask = 0U;\r\n        for (size_t i = 0; i < NUM_BINS_MAX; i++)\r\n        {\r\n            out->bins[i] = NULL;\r\n        }\r\n\r\n        // Limit and align the capacity.\r\n        size_t capacity = size - INSTANCE_SIZE_PADDED;\r\n        if (capacity > FRAGMENT_SIZE_MAX)\r\n        {\r\n            capacity = FRAGMENT_SIZE_MAX;\r\n        }\r\n        while ((capacity % FRAGMENT_SIZE_MIN) != 0)\r\n        {\r\n            O1HEAP_ASSERT(capacity > 0U);\r\n            capacity--;\r\n        }\r\n        O1HEAP_ASSERT((capacity % FRAGMENT_SIZE_MIN) == 0);\r\n        O1HEAP_ASSERT((capacity >= FRAGMENT_SIZE_MIN) && (capacity <= FRAGMENT_SIZE_MAX));\r\n\r\n        // Initialize the root fragment.\r\n        Fragment* const frag = (Fragment*)(void*)(((char*)base) + INSTANCE_SIZE_PADDED);\r\n        O1HEAP_ASSERT((((size_t)frag) % O1HEAP_ALIGNMENT) == 0U);\r\n        frag->header.next = NULL;\r\n        frag->header.prev = NULL;\r\n        frag->header.size = capacity;\r\n        frag->header.used = false;\r\n        frag->next_free = NULL;\r\n        frag->prev_free = NULL;\r\n        rebin(out, frag);\r\n        O1HEAP_ASSERT(out->nonempty_bin_mask != 0U);\r\n\r\n        // Initialize the diagnostics.\r\n        out->diagnostics.capacity = capacity;\r\n        out->diagnostics.allocated = 0U;\r\n        out->diagnostics.peak_allocated = 0U;\r\n        out->diagnostics.peak_request_size = 0U;\r\n        out->diagnostics.oom_count = 0U;\r\n    }\r\n\r\n    return out;\r\n}\r\n\r\nvoid* o1heapAllocate(O1HeapInstance* const handle, const size_t amount)\r\n{\r\n    O1HEAP_ASSERT(handle != NULL);\r\n    O1HEAP_ASSERT(handle->diagnostics.capacity <= FRAGMENT_SIZE_MAX);\r\n    void* out = NULL;\r\n\r\n    // If the amount approaches approx. SIZE_MAX/2, an undetected integer overflow may occur.\r\n    // To avoid that, we do not attempt allocation if the amount exceeds the hard limit.\r\n    // We perform multiple redundant checks to account for a possible unaccounted overflow.\r\n    if (O1HEAP_LIKELY((amount > 0U) && (amount <= (handle->diagnostics.capacity - O1HEAP_ALIGNMENT))))\r\n    {\r\n        // Add the header size and align the allocation size to the power of 2.\r\n        // See \"Timing-Predictable Memory Allocation In Hard Real-Time Systems\", Herter, page 27.\r\n        const size_t fragment_size = roundUpToPowerOf2(amount + O1HEAP_ALIGNMENT);\r\n        O1HEAP_ASSERT(fragment_size <= FRAGMENT_SIZE_MAX);\r\n        O1HEAP_ASSERT(fragment_size >= FRAGMENT_SIZE_MIN);\r\n        O1HEAP_ASSERT(fragment_size >= amount + O1HEAP_ALIGNMENT);\r\n        O1HEAP_ASSERT((fragment_size & (fragment_size - 1U)) == 0U);  // Is power of 2.\r\n\r\n        const uint_fast8_t optimal_bin_index = log2Ceil(fragment_size / FRAGMENT_SIZE_MIN);  // Use CEIL when fetching.\r\n        O1HEAP_ASSERT(optimal_bin_index < NUM_BINS_MAX);\r\n        const size_t candidate_bin_mask = ~(pow2(optimal_bin_index) - 1U);\r\n\r\n        // Find the smallest non-empty bin we can use.\r\n        const size_t suitable_bins = handle->nonempty_bin_mask & candidate_bin_mask;\r\n        const size_t smallest_bin_mask = suitable_bins & ~(suitable_bins - 1U);  // Clear all bits but the lowest.\r\n        if (O1HEAP_LIKELY(smallest_bin_mask != 0))\r\n        {\r\n            O1HEAP_ASSERT((smallest_bin_mask & (smallest_bin_mask - 1U)) == 0U);  // Is power of 2.\r\n            const uint_fast8_t bin_index = log2Floor(smallest_bin_mask);\r\n            O1HEAP_ASSERT(bin_index >= optimal_bin_index);\r\n            O1HEAP_ASSERT(bin_index < NUM_BINS_MAX);\r\n\r\n            // The bin we found shall not be empty, otherwise it's a state divergence (memory corruption?).\r\n            Fragment* const frag = handle->bins[bin_index];\r\n            O1HEAP_ASSERT(frag != NULL);\r\n            O1HEAP_ASSERT(frag->header.size >= fragment_size);\r\n            O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n            O1HEAP_ASSERT(!frag->header.used);\r\n            unbin(handle, frag);\r\n\r\n            // Split the fragment if it is too large.\r\n            const size_t leftover = frag->header.size - fragment_size;\r\n            frag->header.size = fragment_size;\r\n            O1HEAP_ASSERT(leftover < handle->diagnostics.capacity);  // Overflow check.\r\n            O1HEAP_ASSERT(leftover % FRAGMENT_SIZE_MIN == 0U);       // Alignment check.\r\n            if (O1HEAP_LIKELY(leftover >= FRAGMENT_SIZE_MIN))\r\n            {\r\n                Fragment* const new_frag = (Fragment*)(void*)(((char*)frag) + fragment_size);\r\n                O1HEAP_ASSERT(((size_t)new_frag) % O1HEAP_ALIGNMENT == 0U);\r\n                new_frag->header.size = leftover;\r\n                new_frag->header.used = false;\r\n                interlink(new_frag, frag->header.next);\r\n                interlink(frag, new_frag);\r\n                rebin(handle, new_frag);\r\n            }\r\n\r\n            // Update the diagnostics.\r\n            O1HEAP_ASSERT((handle->diagnostics.allocated % FRAGMENT_SIZE_MIN) == 0U);\r\n            handle->diagnostics.allocated += fragment_size;\r\n            O1HEAP_ASSERT(handle->diagnostics.allocated <= handle->diagnostics.capacity);\r\n            if (O1HEAP_LIKELY(handle->diagnostics.peak_allocated < handle->diagnostics.allocated))\r\n            {\r\n                handle->diagnostics.peak_allocated = handle->diagnostics.allocated;\r\n            }\r\n\r\n            // Finalize the fragment we just allocated.\r\n            O1HEAP_ASSERT(frag->header.size >= amount + O1HEAP_ALIGNMENT);\r\n            frag->header.used = true;\r\n\r\n            out = ((char*)frag) + O1HEAP_ALIGNMENT;\r\n        }\r\n    }\r\n\r\n    // Update the diagnostics.\r\n    if (O1HEAP_LIKELY(handle->diagnostics.peak_request_size < amount))\r\n    {\r\n        handle->diagnostics.peak_request_size = amount;\r\n    }\r\n    if (O1HEAP_LIKELY((out == NULL) && (amount > 0U)))\r\n    {\r\n        handle->diagnostics.oom_count++;\r\n    }\r\n\r\n    return out;\r\n}\r\n\r\nvoid o1heapFree(O1HeapInstance* const handle, void* const pointer)\r\n{\r\n    O1HEAP_ASSERT(handle != NULL);\r\n    O1HEAP_ASSERT(handle->diagnostics.capacity <= FRAGMENT_SIZE_MAX);\r\n    if (O1HEAP_LIKELY(pointer != NULL))  // NULL pointer is a no-op.\r\n    {\r\n        Fragment* const frag = (Fragment*)(void*)(((char*)pointer) - O1HEAP_ALIGNMENT);\r\n\r\n        // Check for heap corruption in debug builds.\r\n        O1HEAP_ASSERT(((size_t)frag) % sizeof(Fragment*) == 0U);\r\n        O1HEAP_ASSERT(((size_t)frag) >= (((size_t)handle) + INSTANCE_SIZE_PADDED));\r\n        O1HEAP_ASSERT(((size_t)frag) <=\r\n            (((size_t)handle) + INSTANCE_SIZE_PADDED + handle->diagnostics.capacity - FRAGMENT_SIZE_MIN));\r\n        O1HEAP_ASSERT(frag->header.used);  // Catch double-free\r\n        O1HEAP_ASSERT(((size_t)frag->header.next) % sizeof(Fragment*) == 0U);\r\n        O1HEAP_ASSERT(((size_t)frag->header.prev) % sizeof(Fragment*) == 0U);\r\n        O1HEAP_ASSERT(frag->header.size >= FRAGMENT_SIZE_MIN);\r\n        O1HEAP_ASSERT(frag->header.size <= handle->diagnostics.capacity);\r\n        O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n\r\n        // Even if we're going to drop the fragment later, mark it free anyway to prevent double-free.\r\n        frag->header.used = false;\r\n\r\n        // Update the diagnostics. It must be done before merging because it invalidates the fragment size information.\r\n        O1HEAP_ASSERT(handle->diagnostics.allocated >= frag->header.size);  // Heap corruption check.\r\n        handle->diagnostics.allocated -= frag->header.size;\r\n\r\n        // Merge with siblings and insert the returned fragment into the appropriate bin and update metadata.\r\n        Fragment* const prev = frag->header.prev;\r\n        Fragment* const next = frag->header.next;\r\n        const bool      join_left = (prev != NULL) && (!prev->header.used);\r\n        const bool      join_right = (next != NULL) && (!next->header.used);\r\n        if (join_left && join_right)  // [ prev ][ this ][ next ] => [ ------- prev ------- ]\r\n        {\r\n            unbin(handle, prev);\r\n            unbin(handle, next);\r\n            prev->header.size += frag->header.size + next->header.size;\r\n            frag->header.size = 0;  // Invalidate the dropped fragment headers to prevent double-free.\r\n            next->header.size = 0;\r\n            O1HEAP_ASSERT((prev->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n            interlink(prev, next->header.next);\r\n            rebin(handle, prev);\r\n        }\r\n        else if (join_left)  // [ prev ][ this ][ next ] => [ --- prev --- ][ next ]\r\n        {\r\n            unbin(handle, prev);\r\n            prev->header.size += frag->header.size;\r\n            frag->header.size = 0;\r\n            O1HEAP_ASSERT((prev->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n            interlink(prev, next);\r\n            rebin(handle, prev);\r\n        }\r\n        else if (join_right)  // [ prev ][ this ][ next ] => [ prev ][ --- this --- ]\r\n        {\r\n            unbin(handle, next);\r\n            frag->header.size += next->header.size;\r\n            next->header.size = 0;\r\n            O1HEAP_ASSERT((frag->header.size % FRAGMENT_SIZE_MIN) == 0U);\r\n            interlink(frag, next->header.next);\r\n            rebin(handle, frag);\r\n        }\r\n        else\r\n        {\r\n            rebin(handle, frag);\r\n        }\r\n    }\r\n}\r\n\r\nbool o1heapDoInvariantsHold(const O1HeapInstance* const handle)\r\n{\r\n    O1HEAP_ASSERT(handle != NULL);\r\n    bool valid = true;\r\n\r\n    // Check the bin mask consistency.\r\n    for (size_t i = 0; i < NUM_BINS_MAX; i++)  // Dear compiler, feel free to unroll this loop.\r\n    {\r\n        const bool mask_bit_set = (handle->nonempty_bin_mask & pow2((uint_fast8_t)i)) != 0U;\r\n        const bool bin_nonempty = handle->bins[i] != NULL;\r\n        valid = valid && (mask_bit_set == bin_nonempty);\r\n    }\r\n\r\n    // Create a local copy of the diagnostics struct.\r\n    const O1HeapDiagnostics diag = handle->diagnostics;\r\n\r\n    // Capacity check.\r\n    valid = valid && (diag.capacity <= FRAGMENT_SIZE_MAX) && (diag.capacity >= FRAGMENT_SIZE_MIN) &&\r\n        ((diag.capacity % FRAGMENT_SIZE_MIN) == 0U);\r\n\r\n    // Allocation info check.\r\n    valid = valid && (diag.allocated <= diag.capacity) && ((diag.allocated % FRAGMENT_SIZE_MIN) == 0U) &&\r\n        (diag.peak_allocated <= diag.capacity) && (diag.peak_allocated >= diag.allocated) &&\r\n        ((diag.peak_allocated % FRAGMENT_SIZE_MIN) == 0U);\r\n\r\n    // Peak request check\r\n    valid = valid && ((diag.peak_request_size < diag.capacity) || (diag.oom_count > 0U));\r\n    if (diag.peak_request_size == 0U)\r\n    {\r\n        valid = valid && (diag.peak_allocated == 0U) && (diag.allocated == 0U) && (diag.oom_count == 0U);\r\n    }\r\n    else\r\n    {\r\n        valid = valid &&  // Overflow on summation is possible but safe to ignore.\r\n            (((diag.peak_request_size + O1HEAP_ALIGNMENT) <= diag.peak_allocated) || (diag.oom_count > 0U));\r\n    }\r\n\r\n    return valid;\r\n}\r\n\r\nO1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance* const handle)\r\n{\r\n    O1HEAP_ASSERT(handle != NULL);\r\n    const O1HeapDiagnostics out = handle->diagnostics;\r\n    return out;\r\n}\r\n"
  },
  {
    "path": "thirdparty/o1heap/o1heap.h",
    "content": "// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\r\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation\r\n// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,\r\n// and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions\r\n// of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\r\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\r\n// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\r\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n//\r\n// Copyright (c) 2020 Pavel Kirienko\r\n// Authors: Pavel Kirienko <pavel.kirienko@zubax.com>\r\n//\r\n// READ THE DOCUMENTATION IN README.md.\r\n\r\n#ifndef O1HEAP_H_INCLUDED\r\n#define O1HEAP_H_INCLUDED\r\n\r\n#include <stdbool.h>\r\n#include <stddef.h>\r\n#include <stdint.h>\r\n\r\n#ifdef __cplusplus\r\nextern \"C\" {\r\n#endif\r\n\r\n    /// The semantic version number of this distribution.\r\n#define O1HEAP_VERSION_MAJOR 2\r\n\r\n/// The guaranteed alignment depends on the platform pointer width.\r\n#define O1HEAP_ALIGNMENT (sizeof(void*) * 4U)\r\n\r\n/// The definition is private, so the user code can only operate on pointers. This is done to enforce encapsulation.\r\n    typedef struct O1HeapInstance O1HeapInstance;\r\n\r\n    /// Runtime diagnostic information. This information can be used to facilitate runtime self-testing,\r\n    /// as required by certain safety-critical development guidelines.\r\n    /// If assertion checks are not disabled, the library will perform automatic runtime self-diagnostics that trigger\r\n    /// an assertion failure if a heap corruption is detected.\r\n    /// Health checks and validation can be done with o1heapDoInvariantsHold().\r\n    typedef struct\r\n    {\r\n        /// The total amount of memory available for serving allocation requests (heap size).\r\n        /// The maximum allocation size is (capacity - O1HEAP_ALIGNMENT).\r\n        /// This parameter does not include the overhead used up by O1HeapInstance and arena alignment.\r\n        /// This parameter is constant.\r\n        size_t capacity;\r\n\r\n        /// The amount of memory that is currently allocated, including the per-fragment overhead and size alignment.\r\n        /// For example, if the application requested a fragment of size 1 byte, the value reported here may be 32 bytes.\r\n        size_t allocated;\r\n\r\n        /// The maximum value of 'allocated' seen since initialization. This parameter is never decreased.\r\n        size_t peak_allocated;\r\n\r\n        /// The largest amount of memory that the allocator has attempted to allocate (perhaps unsuccessfully)\r\n        /// since initialization (not including the rounding and the allocator's own per-fragment overhead,\r\n        /// so the total is larger). This parameter is never decreased. The initial value is zero.\r\n        size_t peak_request_size;\r\n\r\n        /// The number of times an allocation request could not be completed due to the lack of memory or\r\n        /// excessive fragmentation. OOM stands for \"out of memory\". This parameter is never decreased.\r\n        uint64_t oom_count;\r\n    } O1HeapDiagnostics;\r\n\r\n    /// The arena base pointer shall be aligned at O1HEAP_ALIGNMENT, otherwise NULL is returned.\r\n    ///\r\n    /// The total heap capacity cannot exceed approx. (SIZE_MAX/2). If the arena size allows for a larger heap,\r\n    /// the excess will be silently truncated away (no error). This is not a realistic use case because a typical\r\n    /// application is unlikely to be able to dedicate that much of the address space for the heap.\r\n    ///\r\n    /// The function initializes a new heap instance allocated in the provided arena, taking some of its space for its\r\n    /// own needs (normally about 40..600 bytes depending on the architecture, but this parameter is not characterized).\r\n    /// A pointer to the newly initialized instance is returned.\r\n    ///\r\n    /// If the provided space is insufficient, NULL is returned.\r\n    ///\r\n    /// An initialized instance does not hold any resources. Therefore, if the instance is no longer needed,\r\n    /// it can be discarded without any de-initialization procedures.\r\n    ///\r\n    /// The heap is not thread-safe; external synchronization may be required.\r\n    O1HeapInstance* o1heapInit(void* const base, const size_t size);\r\n\r\n    /// The semantics follows malloc() with additional guarantees the full list of which is provided below.\r\n    ///\r\n    /// If the allocation request is served successfully, a pointer to the newly allocated memory fragment is returned.\r\n    /// The returned pointer is guaranteed to be aligned at O1HEAP_ALIGNMENT.\r\n    ///\r\n    /// If the allocation request cannot be served due to the lack of memory or its excessive fragmentation,\r\n    /// a NULL pointer is returned.\r\n    ///\r\n    /// The function is executed in constant time.\r\n    /// The allocated memory is NOT zero-filled (because zero-filling is a variable-complexity operation).\r\n    void* o1heapAllocate(O1HeapInstance* const handle, const size_t amount);\r\n\r\n    /// The semantics follows free() with additional guarantees the full list of which is provided below.\r\n    ///\r\n    /// If the pointer does not point to a previously allocated block and is not NULL, the behavior is undefined.\r\n    /// Builds where assertion checks are enabled may trigger an assertion failure for some invalid inputs.\r\n    ///\r\n    /// The function is executed in constant time.\r\n    void o1heapFree(O1HeapInstance* const handle, void* const pointer);\r\n\r\n    /// Performs a basic sanity check on the heap.\r\n    /// This function can be used as a weak but fast method of heap corruption detection.\r\n    /// If the handle pointer is NULL, the behavior is undefined.\r\n    /// The time complexity is constant.\r\n    /// The return value is truth if the heap looks valid, falsity otherwise.\r\n    bool o1heapDoInvariantsHold(const O1HeapInstance* const handle);\r\n\r\n    /// Samples and returns a copy of the diagnostic information, see O1HeapDiagnostics.\r\n    /// This function merely copies the structure from an internal storage, so it is fast to return.\r\n    /// If the handle pointer is NULL, the behavior is undefined.\r\n    O1HeapDiagnostics o1heapGetDiagnostics(const O1HeapInstance* const handle);\r\n\r\n#ifdef __cplusplus\r\n}\r\n#endif\r\n#endif  // O1HEAP_H_INCLUDED\r\n"
  },
  {
    "path": "toolchains/linux-clang.cmake",
    "content": "# Copied and adapted from:\n# https://github.com/microsoft/vcpkg/blob/7adc2e4d49e8d0efc07a369079faa6bc3dbb90f3/scripts/toolchains/linux.cmake\n#\n# The original seems to be written only for GCC.  This version is changed to use clang and\n# LLVM bin tools.\n\nif(NOT _VCPKG_LINUX_CLANG_TOOLCHAIN)\n    set(_VCPKG_LINUX_CLANG_TOOLCHAIN 1)\n\n    if(POLICY CMP0056)\n        cmake_policy(SET CMP0056 NEW)\n    endif()\n    if(POLICY CMP0066)\n        cmake_policy(SET CMP0066 NEW)\n    endif()\n    if(POLICY CMP0067)\n        cmake_policy(SET CMP0067 NEW)\n    endif()\n    if(POLICY CMP0137)\n        cmake_policy(SET CMP0137 NEW)\n    endif()\n    list(APPEND CMAKE_TRY_COMPILE_PLATFORM_VARIABLES\n        VCPKG_CRT_LINKAGE VCPKG_TARGET_ARCHITECTURE\n        VCPKG_C_FLAGS VCPKG_CXX_FLAGS\n        VCPKG_C_FLAGS_DEBUG VCPKG_CXX_FLAGS_DEBUG\n        VCPKG_C_FLAGS_RELEASE VCPKG_CXX_FLAGS_RELEASE\n        VCPKG_LINKER_FLAGS VCPKG_LINKER_FLAGS_RELEASE VCPKG_LINKER_FLAGS_DEBUG\n    )\n\n    set(CMAKE_SYSTEM_NAME Linux CACHE STRING \"\")\n\n    # Set compiler to clang\n    set(CMAKE_C_COMPILER clang)\n    set(CMAKE_CXX_COMPILER clang++)\n    SET(CMAKE_ASM_COMPILER clang)\n\n    # Pick target architecture for clang\n    if(VCPKG_TARGET_ARCHITECTURE STREQUAL \"x64\")\n        set(CMAKE_SYSTEM_PROCESSOR x86_64 CACHE STRING \"\")\n        set(CLANG_TARGET x86_64-unknown-linux-gnu)\n    elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL \"x86\")\n        set(CMAKE_SYSTEM_PROCESSOR i686 CACHE STRING \"\")\n        set(CLANG_TARGET i686-unknown-linux-gnu)\n    elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL \"arm\")\n        set(CMAKE_SYSTEM_PROCESSOR armv7l CACHE STRING \"\")\n        set(CLANG_TARGET armv7-unknown-linux-gnueabihf)\n    elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL \"arm64\")\n        set(CMAKE_SYSTEM_PROCESSOR aarch64 CACHE STRING \"\")\n        set(CLANG_TARGET aarch64-unknown-linux-gnu)\n    endif()\n    if(DEFINED CLANG_TARGET)\n        set(CMAKE_C_COMPILER_TARGET ${CLANG_TARGET})\n        set(CMAKE_CXX_COMPILER_TARGET ${CLANG_TARGET})\n        set(CMAKE_ASM_COMPILER_TARGET ${CLANG_TARGET})\n    endif()\n\n    if(CMAKE_HOST_SYSTEM_NAME STREQUAL \"Linux\" AND CMAKE_SYSTEM_PROCESSOR STREQUAL CMAKE_HOST_SYSTEM_PROCESSOR)\n        set(CMAKE_CROSSCOMPILING OFF CACHE BOOL \"\")\n    endif()\n\n    string(APPEND CMAKE_C_FLAGS_INIT \" -fPIC ${VCPKG_C_FLAGS} \")\n    string(APPEND CMAKE_CXX_FLAGS_INIT \" -fPIC ${VCPKG_CXX_FLAGS} \")\n    string(APPEND CMAKE_C_FLAGS_DEBUG_INIT \" ${VCPKG_C_FLAGS_DEBUG} \")\n    string(APPEND CMAKE_CXX_FLAGS_DEBUG_INIT \" ${VCPKG_CXX_FLAGS_DEBUG} \")\n    string(APPEND CMAKE_C_FLAGS_RELEASE_INIT \" ${VCPKG_C_FLAGS_RELEASE} \")\n    string(APPEND CMAKE_CXX_FLAGS_RELEASE_INIT \" ${VCPKG_CXX_FLAGS_RELEASE} \")\n\n    # Use LLVM's lld linker\n    set(CMAKE_LINKER_TYPE LLD)\n\n    string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT \" ${VCPKG_LINKER_FLAGS} \")\n    string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT \" ${VCPKG_LINKER_FLAGS} \")\n    string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT \" ${VCPKG_LINKER_FLAGS} \")\n    if(VCPKG_CRT_LINKAGE STREQUAL \"static\")\n        string(APPEND CMAKE_MODULE_LINKER_FLAGS_INIT \"-static \")\n        string(APPEND CMAKE_SHARED_LINKER_FLAGS_INIT \"-static \")\n        string(APPEND CMAKE_EXE_LINKER_FLAGS_INIT \"-static \")\n    endif()\n    string(APPEND CMAKE_MODULE_LINKER_FLAGS_DEBUG_INIT \" ${VCPKG_LINKER_FLAGS_DEBUG} \")\n    string(APPEND CMAKE_SHARED_LINKER_FLAGS_DEBUG_INIT \" ${VCPKG_LINKER_FLAGS_DEBUG} \")\n    string(APPEND CMAKE_EXE_LINKER_FLAGS_DEBUG_INIT \" ${VCPKG_LINKER_FLAGS_DEBUG} \")\n    string(APPEND CMAKE_MODULE_LINKER_FLAGS_RELEASE_INIT \" ${VCPKG_LINKER_FLAGS_RELEASE} \")\n    string(APPEND CMAKE_SHARED_LINKER_FLAGS_RELEASE_INIT \" ${VCPKG_LINKER_FLAGS_RELEASE} \")\n    string(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT \" ${VCPKG_LINKER_FLAGS_RELEASE} \")\n    string(APPEND CMAKE_ASM_FLAGS_INIT \" ${VCPKG_C_FLAGS} \")\nendif()\n"
  },
  {
    "path": "tools/CMakeLists.txt",
    "content": "option(MARATHON_RECOMP_OPTIMIZE_TOOLS \"Apply compiler optimizations to build tools.\" ON)\nif (MARATHON_RECOMP_OPTIMIZE_TOOLS)\n    if (WIN32)\n        add_compile_options(/O2)\n    else()\n        add_compile_options(-O3)\n    endif()\n    set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON)\nendif()\n\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/bc_diff)\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/file_to_c)\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/fshasher)\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/u8extract)\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/x_decompress)\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/XenonRecomp)\nadd_subdirectory(${MARATHON_RECOMP_TOOLS_ROOT}/XenosRecomp)\n"
  },
  {
    "path": "tools/bc_diff/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.20)\n\nproject(\"bc_diff\")\nset(CMAKE_CXX_STANDARD 17)\n\nadd_executable(bc_diff \"bc_diff.cpp\")\nadd_compile_definitions(bc_diff PRIVATE _CRT_SECURE_NO_WARNINGS)\n\ntarget_link_libraries(bc_diff PRIVATE xxHash::xxhash)\n"
  },
  {
    "path": "tools/bc_diff/bc_diff.cpp",
    "content": "#include \"bc_diff.h\"\n#include <algorithm>\n#include <cassert>\n#include <cstring>\n#include <filesystem>\n#include <vector>\n#include <xxhash.h>\n\nstatic std::vector<uint8_t> readAllBytes(const char* filePath)\n{\n    FILE* file = fopen(filePath, \"rb\");\n\n    if (!file)\n        return {};\n\n    fseek(file, 0, SEEK_END);\n\n    long fileSize = ftell(file);\n    fseek(file, 0, SEEK_SET);\n\n    std::vector<uint8_t> data(fileSize);\n    fread(data.data(), 1, fileSize, file);\n\n    fclose(file);\n\n    return data;\n}\n\nint main(int argc, char** argv)\n{\n    if (argc != 4)\n    {\n        printf(\"Usage: %s [old directory] [new directory] [destination file]\", argv[0]);\n        return 0;\n    }\n\n    // Debug configuration doesn't compile without this???\n    assert(argc == 4);\n\n    std::filesystem::path oldDirectoryPath = argv[1];\n    std::filesystem::path newDirectoryPath = argv[2];\n\n    std::vector<BlockCompressionDiffPatchEntry> entries;\n    std::vector<BlockCompressionDiffPatch> patches;\n    std::vector<uint8_t> patchBytes;\n\n    for (auto& oldFile : std::filesystem::recursive_directory_iterator(oldDirectoryPath))\n    {\n        auto newFile = newDirectoryPath / std::filesystem::relative(oldFile, oldDirectoryPath);\n        if (!std::filesystem::exists(newFile))\n        {\n            fprintf(stderr, \"Cannot locate %s\\n\", newFile.string().c_str());\n            continue;\n        }\n\n        auto oldFileData = readAllBytes(oldFile.path().string().c_str());\n        auto newFileData = readAllBytes(newFile.string().c_str());\n\n        constexpr size_t BC_STRIDE = 8;\n\n        if (oldFileData.size() != newFileData.size())\n        {\n            fprintf(stderr, \"%s does not match %s in file size\\n\", oldFile.path().string().c_str(), newFile.string().c_str());\n            continue;\n        }\n\n        if ((oldFileData.size() % BC_STRIDE) != 0)\n        {\n            fprintf(stderr, \"%s is not aligned to %d bytes\\n\", oldFile.path().string().c_str(), BC_STRIDE);\n            continue;\n        }\n\n        if (oldFileData.size() >= BC_STRIDE)\n        {\n            size_t patchIndex = patches.size();\n\n            for (size_t i = 0; i < oldFileData.size() - BC_STRIDE + 1; i += BC_STRIDE)\n            {\n                if (memcmp(&oldFileData[i], &newFileData[i], BC_STRIDE) == 0)\n                    continue;\n\n                size_t patchBytesOffset = patchBytes.size();\n                patchBytes.insert(patchBytes.end(), newFileData.begin() + i, newFileData.begin() + i + BC_STRIDE);\n\n                if (patchIndex >= patches.size() || ((patches.back().destinationOffset + patches.back().patchBytesSize) != i))\n                {\n                    auto& patch = patches.emplace_back();\n                    patch.destinationOffset = i;\n                    patch.patchBytesOffset = patchBytesOffset;\n                    patch.patchBytesSize = BC_STRIDE;\n                }\n                else\n                {\n                    patches.back().patchBytesSize += BC_STRIDE;\n                }\n            }\n\n            size_t patchCount = patches.size() - patchIndex;\n            if (patchCount != 0)\n            {\n                auto& entry = entries.emplace_back();\n                entry.hash = XXH3_64bits(oldFileData.data(), oldFileData.size());\n                entry.patchesOffset = patchIndex * sizeof(BlockCompressionDiffPatch);\n                entry.patchCount = patchCount;\n\n                printf(\"Generated BC patch for %s\\n\", oldFile.path().string().c_str());\n            }\n            else\n            {\n                printf(\"Skipping %s, files are identical\\n\", oldFile.path().string().c_str()); \n            }\n        }\n    }\n\n    std::sort(entries.begin(), entries.end(), [](auto& lhs, auto& rhs) { return lhs.hash < rhs.hash; });\n\n    BlockCompressionDiffPatchHeader header;\n    header.entriesOffset = sizeof(BlockCompressionDiffPatchHeader);\n    header.entryCount = entries.size();\n\n    size_t patchesOffset = header.entriesOffset + sizeof(BlockCompressionDiffPatchEntry) * entries.size();\n    size_t patchBytesOffset = patchesOffset + sizeof(BlockCompressionDiffPatch) * patches.size();\n\n    for (auto& entry : entries)\n        entry.patchesOffset += patchesOffset;\n\n    for (auto& patch : patches)\n        patch.patchBytesOffset += patchBytesOffset;\n\n    FILE* file = fopen(argv[3], \"wb\");\n    if (!file)\n    {\n        fprintf(stderr, \"Cannot open %s for writing\\n\", argv[3]);\n        return 1;\n    }\n\n    fwrite(&header, sizeof(header), 1, file);\n    fwrite(entries.data(), sizeof(BlockCompressionDiffPatchEntry), entries.size(), file);\n    fwrite(patches.data(), sizeof(BlockCompressionDiffPatch), patches.size(), file);\n    fwrite(patchBytes.data(), 1, patchBytes.size(), file);\n    fclose(file);\n\n    return 0;\n}\n"
  },
  {
    "path": "tools/bc_diff/bc_diff.h",
    "content": "#pragma once \n\n#include <cstdint>\n\nstruct BlockCompressionDiffPatch\n{\n    uint32_t destinationOffset;\n    uint32_t patchBytesOffset;\n    uint32_t patchBytesSize;\n};\n\nstruct BlockCompressionDiffPatchEntry\n{\n    uint64_t hash;\n    uint32_t patchesOffset;\n    uint32_t patchCount;\n};\n\nstruct BlockCompressionDiffPatchHeader\n{\n    uint32_t entriesOffset;\n    uint32_t entryCount;\n};\n"
  },
  {
    "path": "tools/file_to_c/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.20)\n\ninclude(CMakeParseArguments)\n\nproject(\"file_to_c\")\nset(CMAKE_CXX_STANDARD 17)\n\nadd_executable(file_to_c \"file_to_c.cpp\")\n\ntarget_link_libraries(file_to_c PRIVATE $<IF:$<TARGET_EXISTS:libzstd_static>,libzstd_static,libzstd_shared>)\n"
  },
  {
    "path": "tools/file_to_c/file_to_c.cpp",
    "content": "/*\n    MIT License\n    \n    Copyright (c) 2024 RT64 Contributors\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\n#include <algorithm>\n#include <filesystem>\n#include <fstream>\n#include <cstdio>\n#include <vector>\n#include <zstd.h>\n\nstd::vector<char> read_file(const char* path) {\n    std::ifstream input_file{path, std::ios::binary};\n    std::vector<char> ret{};\n\n    if (!input_file.good()) {\n        return ret;\n    }\n\n    // Get the length of the file\n    input_file.seekg(0, std::ios::end);\n    ret.resize(input_file.tellg());\n    \n    // Read the file contents into the vector\n    input_file.seekg(0, std::ios::beg);\n    input_file.read(ret.data(), ret.size());\n\n    return ret;\n}\n\nvoid create_parent_if_needed(const char* path) {\n    std::filesystem::path parent_path = std::filesystem::path{path}.parent_path();\n    if (!parent_path.empty()) {\n        std::filesystem::create_directories(parent_path);\n    }\n}\n\nint main(int argc, const char** argv) {\n    if (argc != 6) {\n        printf(\"Usage: %s [input file] [array name] [compression type] [output C file] [output C header]\\n\", argv[0]);\n        return EXIT_SUCCESS;\n    }\n\n    const char* input_path = argv[1];\n    const char* array_name = argv[2];\n    std::string compression_type = argv[3];\n    const char* output_c_path = argv[4];\n    const char* output_h_path = argv[5];\n\n    // Read the input file's contents\n    std::vector<char> contents = read_file(input_path);\n\n    if (contents.empty()) {\n        fprintf(stderr, \"Failed to open file %s! (Or it's empty)\\n\", input_path);\n        return EXIT_FAILURE;\n    }\n\n    // Compress if requested.\n    std::vector<char> compressed_contents;\n    std::transform(compression_type.begin(), compression_type.end(), compression_type.begin(), tolower);\n\n    if (compression_type == \"zstd\") {\n        size_t bound_size = ZSTD_compressBound(contents.size());\n        compressed_contents.resize(bound_size);\n\n        size_t compressed_size = ZSTD_compress(compressed_contents.data(), bound_size, contents.data(), contents.size(), ZSTD_maxCLevel());\n        compressed_contents.resize(compressed_size);\n    }\n    else if (compression_type != \"none\") {\n        fprintf(stderr, \"Unknown compression type %s!\", compression_type.c_str());\n        return EXIT_FAILURE;\n    }\n\n    // Create the output directories if they don't exist\n    create_parent_if_needed(output_c_path);\n    create_parent_if_needed(output_h_path);\n\n    // Write the C file with the array\n    std::vector<char>& contents_to_write = !compressed_contents.empty() ? compressed_contents : contents;\n    {\n        std::ofstream output_c_file{output_c_path};\n        output_c_file << \"extern unsigned char \" << array_name << \"[\" << contents_to_write.size() << \"];\\n\";\n        output_c_file << \"unsigned char \" << array_name << \"[\" << contents_to_write.size() << \"] = {\";\n\n        for (char x : contents_to_write) {\n            output_c_file << (int)(unsigned char)x << \", \";\n        }\n\n        output_c_file << \"};\\n\";\n\n        // Write decompressed size.\n        if (!compressed_contents.empty()) {\n            output_c_file << \"extern unsigned long long \" << array_name << \"_uncompressed_size;\\n\";\n            output_c_file << \"unsigned long long \" << array_name << \"_uncompressed_size = \" << contents.size() << \";\\n\";\n        }\n    }\n\n    // Write the header file with the extern array\n    {\n        std::ofstream output_h_file{output_h_path};\n        output_h_file <<\n            \"#ifdef __cplusplus\\n\"\n            \"  extern \\\"C\\\" {\\n\"\n            \"#endif\\n\"\n            \"extern unsigned char \" << array_name << \"[\" << contents_to_write.size() << \"];\\n\";\n\n        // Write decompressed size.\n        if (!compressed_contents.empty()) {\n            output_h_file << \"extern unsigned long long \" << array_name << \"_uncompressed_size;\\n\";\n        }\n\n        output_h_file <<\n            \"#ifdef __cplusplus\\n\"\n            \"  }\\n\"\n            \"#endif\\n\";\n    }\n}\n"
  },
  {
    "path": "tools/fshasher/CMakeLists.txt",
    "content": "project(\"fshasher\")\n\nadd_executable(fshasher \"fshasher.cpp\")\n\ntarget_link_libraries(fshasher PRIVATE xxHash::xxhash)\n"
  },
  {
    "path": "tools/fshasher/fshasher.cpp",
    "content": "// \n// fshasher - CLI tool to generate a hash map from a file system.\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#include <cstdio>\n#include <filesystem>\n#include <fstream>\n#include <iostream>\n#include <list>\n#include <map>\n#include <set>\n\n#include <xxh3.h>\n\n#include \"plainargs.h\"\n\nvoid showHelp() {\n    std::cout << \"fshasher --directory <directory1 directory2 ...> --source <source file> --header <header file> --variable <variable name>\" << std::endl;\n}\n\nint process(const std::list<std::filesystem::path> &searchDirectories, std::ofstream &outputSourceStream, std::ofstream &outputHeaderStream, const std::string &variableName) {\n    auto writeExterns = [&](std::ofstream &outputStream)\n    {\n        outputStream << \"extern const uint64_t \" << variableName << \"Hashes[];\" << std::endl;\n        outputStream << \"extern const std::pair<const char *, uint32_t> \" << variableName << \"Files[];\" << std::endl;\n        outputStream << \"extern const size_t \" << variableName << \"FilesSize;\" << std::endl << std::endl;\n    };\n\n    // Generate header.\n    outputHeaderStream << \"// File automatically generated by fshasher\" << std::endl << std::endl;\n    outputHeaderStream << \"#pragma once\" << std::endl << std::endl;\n    outputHeaderStream << \"#include <utility>\" << std::endl << std::endl;\n    writeExterns(outputHeaderStream);\n\n    if (outputHeaderStream.bad()) \n    {\n        std::cerr << \"Failed to write to output header.\" << std::endl;\n        return 1;\n    }\n\n    outputSourceStream << \"// File automatically generated by fshasher\" << std::endl << std::endl;\n    outputSourceStream << \"#include <utility>\" << std::endl << std::endl;\n    writeExterns(outputSourceStream);\n\n    std::map<std::u8string, std::set<uint64_t>> fileHashSets;\n    char fileData[65536];\n    XXH3_state_t xxh3;\n    for (const std::filesystem::path &searchDirectory : searchDirectories)\n    {\n        if (!std::filesystem::is_directory(searchDirectory))\n        {\n            std::cerr << \"Specified directory \" << searchDirectory << \" does not exist.\" << std::endl;\n            return 1;\n        }\n\n        for (const std::filesystem::directory_entry &entry : std::filesystem::recursive_directory_iterator(searchDirectory))\n        {\n            if (!entry.is_regular_file())\n            {\n                continue;\n            }\n\n            std::filesystem::path entryPath = entry.path();\n            std::filesystem::path entryRelative = std::filesystem::relative(entryPath, searchDirectory);\n            std::ifstream entryStream(entryPath, std::ios::binary);\n            if (!entryStream.is_open())\n            {\n                std::cerr << \"Could not open \" << entryPath << \" for reading.\" << std::endl;\n                return 1;\n            }\n\n            std::cout << \"Reading \" << entryRelative << \".\" << std::endl;\n            XXH3_64bits_reset(&xxh3);\n            while (!entryStream.eof() && !entryStream.bad())\n            {\n                entryStream.read(fileData, sizeof(fileData));\n                XXH3_64bits_update(&xxh3, fileData, entryStream.gcount());\n            }\n\n            if (entryStream.bad())\n            {\n                std::cerr << \"Could not read \" << entryPath << \" successfully.\" << std::endl;\n                return 1;\n            }\n\n            std::u8string entryRelativeU8 = entryRelative.u8string();\n            std::replace(entryRelativeU8.begin(), entryRelativeU8.end(), '\\\\', '/');\n            fileHashSets[entryRelativeU8].insert(XXH3_64bits_digest(&xxh3));\n        }\n    }\n\n    outputSourceStream << \"const uint64_t \" << variableName << \"Hashes[] = {\" << std::endl;\n\n    for (auto &it : fileHashSets)\n    {\n        for (uint64_t hash : it.second)\n        {\n            outputSourceStream << \"    \" << hash << \"ULL,\" << std::endl;\n        }\n\n        if (outputSourceStream.bad())\n        {\n            std::cerr << \"Failed to write to output source.\" << std::endl;\n            return 1;\n        }\n    }\n\n    outputSourceStream << \"};\" << std::endl << std::endl;\n    outputSourceStream << \"const std::pair<const char *, uint32_t> \" << variableName << \"Files[] = {\" << std::endl;\n\n    for (const auto &it : fileHashSets)\n    {\n        outputSourceStream << \"    { \\\"\" << (const char *)(it.first.c_str()) << \"\\\", \" << it.second.size() << \" },\" << std::endl;\n        if (outputSourceStream.bad())\n        {\n            std::cerr << \"Failed to write to output source.\" << std::endl;\n            return 1;\n        }\n    }\n\n    outputSourceStream << \"};\" << std::endl << std::endl;\n    outputSourceStream << \"const size_t \" << variableName << \"FilesSize = std::size(\" << variableName << \"Files);\" << std::endl;\n\n    if (outputSourceStream.bad())\n    {\n        std::cerr << \"Failed to write to output source.\" << std::endl;\n        return 1;\n    }\n\n    return 0;\n}\n\nint main(int argc, char *argv[])\n{\n    plainargs::Result argsResult = plainargs::parse(argc, argv);\n    std::vector<std::string> directories = argsResult.getValues(\"directory\", \"d\");\n    std::string variable = argsResult.getValue(\"variable\", \"v\");\n    std::string source = argsResult.getValue(\"source\", \"s\");\n    std::string header = argsResult.getValue(\"header\", \"h\");\n    if (directories.empty() || variable.empty() || source.empty() || header.empty())\n    {\n        showHelp();\n        return 1;\n    }\n\n    std::filesystem::path sourcePath(source);\n    std::ofstream sourceStream(sourcePath);\n    if (!sourceStream.is_open())\n    {\n        std::cerr << \"Could not open \" << sourcePath << \" for writing.\" << std::endl;\n        return 1;\n    }\n\n    std::filesystem::path headerPath(header);\n    std::ofstream headerStream(headerPath);\n    if (!headerStream.is_open())\n    {\n        std::cerr << \"Could not open \" << headerPath << \" for writing.\" << std::endl;\n        return 1;\n    }\n\n    std::list<std::filesystem::path> searchDirectories;\n    for (std::string &directory : directories)\n    {\n        searchDirectories.emplace_back(directory);\n    }\n\n    int resultCode = process(searchDirectories, sourceStream, headerStream, variable);\n    sourceStream.close();\n    headerStream.close();\n\n    if (resultCode != 0)\n    {\n        std::cerr << \"Failed to generate \" << sourcePath << \"and\" << headerPath << \".\" << std::endl;\n        std::filesystem::remove(sourcePath);\n        std::filesystem::remove(headerPath);\n    }\n\n    return resultCode;\n}\n"
  },
  {
    "path": "tools/fshasher/plainargs.h",
    "content": "// \n// plainargs - A very plain CLI arguments parsing header-only library.\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#include <algorithm>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\nnamespace plainargs {\n    class Result {\n    private:\n        struct Option {\n            uint32_t keyIndex;\n            uint32_t valueCount;\n        };\n\n        std::string directory;\n        std::vector<std::string> arguments;\n        std::vector<Option> options;\n        std::unordered_map<std::string, uint32_t> shortKeyMap;\n        std::unordered_map<std::string, uint32_t> longKeyMap;\n    public:\n        // Arguments are the same as main().\n        Result(int argc, char *argv[]) {\n            if (argc < 1) {\n                return;\n            }\n\n            directory = argv[0];\n\n            arguments.resize(size_t(argc - 1));\n            for (uint32_t i = 1; i < uint32_t(argc); i++) {\n                std::string &argument = arguments[i - 1];\n                argument = std::string(argv[i]);\n\n                if (!argument.empty()) {\n                    bool shortKey = (argument.size() > 1) && (argument[0] == '-');\n                    bool longKey = (argument.size() > 2) && (argument[0] == '-') && (argument[1] == '-');\n                    if (longKey) {\n                        longKeyMap[argument.substr(2)] = uint32_t(options.size());\n                        options.emplace_back(Option{ i - 1, 0 });\n                    }\n                    else if (shortKey) {\n                        shortKeyMap[argument.substr(1)] = uint32_t(options.size());\n                        options.emplace_back(Option{ i - 1, 0 });\n                    }\n                    else if (!options.empty()) {\n                        options.back().valueCount++;\n                    }\n                }\n            }\n        }\n\n        // Return all the values associated to the long key or the short key in order.\n        std::vector<std::string> getValues(const std::string &longKey, const std::string &shortKey = \"\", uint32_t maxValues = 0) const {\n            std::vector<std::string> values;\n            auto optionIt = options.end();\n            if (!longKey.empty()) {\n                auto it = longKeyMap.find(longKey);\n                if (it != longKeyMap.end()) {\n                    optionIt = options.begin() + it->second;\n                }\n            }\n\n            if ((optionIt == options.end()) && !shortKey.empty()) {\n                auto it = shortKeyMap.find(shortKey);\n                if (it != shortKeyMap.end()) {\n                    optionIt = options.begin() + it->second;\n                }\n            }\n\n            if (optionIt != options.end()) {\n                uint32_t valueCount = optionIt->valueCount;\n                if ((maxValues > 0) && (valueCount > maxValues)) {\n                    valueCount = maxValues;\n                }\n\n                values.resize(valueCount);\n                for (uint32_t i = 0; i < valueCount; i++) {\n                    values[i] = arguments[optionIt->keyIndex + i + 1];\n                }\n            }\n\n            return values;\n        }\n\n        std::string getValue(const std::string &longKey, const std::string &shortKey = \"\") const {\n            std::vector<std::string> values = getValues(longKey, shortKey, 1);\n            return !values.empty() ? values.front() : std::string();\n        }\n\n        // Return whether an option with the long key or short key was specified.\n        bool hasOption(const std::string &longKey, const std::string &shortKey = \"\") const {\n            if (!longKey.empty() && (longKeyMap.find(longKey) != longKeyMap.end())) {\n                return true;\n            }\n            else if (!shortKey.empty() && (shortKeyMap.find(shortKey) != shortKeyMap.end())) {\n                return true;\n            }\n            else {\n                return false;\n            }\n        }\n\n        // Corresponds to argv[0].\n        const std::string &getDirectory() const {\n            return directory;\n        }\n\n        // No bounds checking, must be a valid index.\n        const std::string getArgument(uint32_t index) const {\n            return arguments[index];\n        }\n\n        // Will be one less than argc.\n        uint32_t getArgumentCount() const {\n            return arguments.size();\n        }\n    };\n\n    // Parse and return the arguments in a structure that can be queried easily. Does not perform any validation of the arguments.\n    Result parse(int argc, char *argv[]) {\n        return Result(argc, argv);\n    }\n};\n"
  },
  {
    "path": "tools/u8extract/CMakeLists.txt",
    "content": "project(\"u8extract\")\nset(CMAKE_CXX_STANDARD 17)\n\nfind_package(ZLIB REQUIRED)\n\nadd_executable(u8extract\n    \"u8extract.cpp\"\n)\n\ntarget_link_libraries(u8extract PRIVATE\n    ZLIB::ZLIB\n)\n"
  },
  {
    "path": "tools/u8extract/u8extract.cpp",
    "content": "#include <iostream>\n#include <fstream>\n#include <vector>\n#include <string>\n#include <filesystem>\n#include <cstring>\n#include <zlib.h>\n\nclass U8Archive {\nprivate:\n    static constexpr uint32_t SIGNATURE = 0x55AA382D;\n    static constexpr uint32_t TYPE_MASK = 0xFF000000;\n    static constexpr uint32_t NAME_OFFSET_MASK = 0x00FFFFFF;\n\n    enum class EntryType : uint8_t {\n        File = 0,\n        Directory = 1\n    };\n\n    struct Entry {\n        uint32_t flags;\n        uint32_t offset;\n        uint32_t length;\n        uint32_t uncompressed_size;\n        std::string name;\n\n        EntryType getType() const {\n            return static_cast<EntryType>((flags & TYPE_MASK) >> 24);\n        }\n\n        uint32_t getNameOffset() const {\n            return flags & NAME_OFFSET_MASK;\n        }\n\n        bool isCompressed() const {\n            return length != 0 && uncompressed_size != 0;\n        }\n    };\n\n    std::ifstream file;\n    std::vector<Entry> entries;\n    uint32_t data_offset;\n\n    uint32_t readU32() {\n        uint32_t value;\n        file.read(reinterpret_cast<char*>(&value), 4);\n        return __builtin_bswap32(value); // Big-endian to host\n    }\n\n    std::string readNullTerminatedString() {\n        std::string result;\n        char ch;\n        while (file.get(ch) && ch != '\\0') {\n            result += ch;\n        }\n        return result;\n    }\n\n    void decompressZlib(const std::vector<uint8_t>& compressed, std::vector<uint8_t>& decompressed) {\n        z_stream stream = {};\n        stream.next_in = const_cast<uint8_t*>(compressed.data());\n        stream.avail_in = compressed.size();\n        stream.next_out = decompressed.data();\n        stream.avail_out = decompressed.size();\n\n        if (inflateInit(&stream) != Z_OK) {\n            throw std::runtime_error(\"Failed to initialize zlib\");\n        }\n\n        int ret = inflate(&stream, Z_FINISH);\n        inflateEnd(&stream);\n\n        if (ret != Z_STREAM_END) {\n            throw std::runtime_error(\"Failed to decompress data\");\n        }\n    }\n\n    void parseEntries(size_t entry_index, const std::string& base_path) {\n        const Entry& entry = entries[entry_index];\n        std::string full_path = base_path.empty() ? entry.name : base_path + \"/\" + entry.name;\n\n        if (entry.getType() == EntryType::Directory) {\n            // Create directory\n            if (!entry.name.empty()) {\n                std::filesystem::create_directories(full_path);\n            }\n\n            // Process children\n            size_t child_index = entry_index + 1;\n            while (child_index < entry.length) {\n                child_index = parseEntriesRecursive(child_index, full_path);\n            }\n\n            return;\n        } else {\n            // Extract file\n            extractFile(entry, full_path);\n        }\n    }\n\n    size_t parseEntriesRecursive(size_t entry_index, const std::string& base_path) {\n        const Entry& entry = entries[entry_index];\n        std::string full_path = base_path.empty() ? entry.name : base_path + \"/\" + entry.name;\n\n        if (entry.getType() == EntryType::Directory) {\n            // Create directory\n            std::filesystem::create_directories(full_path);\n\n            // Process children\n            size_t child_index = entry_index + 1;\n            while (child_index < entry.length) {\n                child_index = parseEntriesRecursive(child_index, full_path);\n            }\n\n            return entry.length;\n        } else {\n            // Extract file\n            extractFile(entry, full_path);\n            return entry_index + 1;\n        }\n    }\n\n    void extractFile(const Entry& entry, const std::string& path) {\n        // Seek to file data\n        file.seekg(entry.offset);\n\n        // Read file data\n        std::vector<uint8_t> data(entry.length);\n        file.read(reinterpret_cast<char*>(data.data()), entry.length);\n\n        // Decompress if necessary\n        if (entry.isCompressed()) {\n            std::vector<uint8_t> decompressed(entry.uncompressed_size);\n            decompressZlib(data, decompressed);\n            data = std::move(decompressed);\n        }\n\n        // Write to file\n        std::ofstream out(path, std::ios::binary);\n        out.write(reinterpret_cast<const char*>(data.data()), data.size());\n        out.close();\n    }\n\npublic:\n    void load(const std::string& filename) {\n        file.open(filename, std::ios::binary);\n        if (!file) {\n            throw std::runtime_error(\"Failed to open file: \" + filename);\n        }\n\n        // Read header\n        uint32_t signature = readU32();\n        if (signature != SIGNATURE) {\n            throw std::runtime_error(\"Invalid U8 archive signature\");\n        }\n\n        uint32_t entries_offset = readU32();\n        uint32_t entries_length = readU32();\n        data_offset = readU32();\n\n        // Skip unknown values\n        file.seekg(16, std::ios::cur);\n\n        // Read root entry\n        file.seekg(entries_offset);\n        Entry root;\n        root.flags = readU32();\n        root.offset = readU32();\n        root.length = readU32();\n        root.uncompressed_size = readU32();\n        entries.push_back(root);\n\n        // Read remaining entries\n        for (uint32_t i = 1; i < root.length; ++i) {\n            Entry entry;\n            entry.flags = readU32();\n            entry.offset = readU32();\n            entry.length = readU32();\n            entry.uncompressed_size = readU32();\n            entries.push_back(entry);\n        }\n\n        // Calculate string table offset\n        uint32_t string_table_offset = entries_offset + (root.length * 16);\n\n        // Read entry names\n        for (auto& entry : entries) {\n            file.seekg(string_table_offset + entry.getNameOffset());\n            entry.name = readNullTerminatedString();\n        }\n    }\n\n    void extract(const std::string& output_dir) {\n        if (entries.empty()) {\n            throw std::runtime_error(\"No archive loaded\");\n        }\n\n        // Create output directory\n        std::filesystem::create_directories(output_dir);\n\n        // Start extraction from root\n        parseEntries(0, output_dir);\n    }\n\n    ~U8Archive() {\n        if (file.is_open()) {\n            file.close();\n        }\n    }\n};\n\nint main(int argc, char* argv[]) {\n    if (argc != 3) {\n        std::cerr << \"Usage: \" << argv[0] << \" <archive.arc> <output_directory>\" << std::endl;\n        return 1;\n    }\n\n    try {\n        U8Archive archive;\n        archive.load(argv[1]);\n        archive.extract(argv[2]);\n        std::cout << \"Extraction complete!\" << std::endl;\n    } catch (const std::exception& e) {\n        std::cerr << \"Error: \" << e.what() << std::endl;\n        return 1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "tools/x_decompress/CMakeLists.txt",
    "content": "project(\"x_decompress\")\nset(CMAKE_CXX_STANDARD 17)\n\nadd_executable(x_decompress\n    \"x_decompress.cpp\"\n    \"${MARATHON_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty/libmspack/libmspack/mspack/lzxd.c\"\n)\n\ntarget_include_directories(x_decompress\n    PRIVATE \"${MARATHON_RECOMP_TOOLS_ROOT}/XenonRecomp/thirdparty/libmspack/libmspack/mspack\"\n)\n"
  },
  {
    "path": "tools/x_decompress/x_decompress.cpp",
    "content": "#include <algorithm>\n#include <cstdint>\n#include <cstddef>\n#include <cstring>\n#include <vector>\n#include <fstream>\n#include <cassert>\n#include <mspack.h>\n#include <lzx.h>\n\nstatic std::vector<uint8_t> readAllBytes(const char* path)\n{\n    std::ifstream file{ path, std::ios::binary };\n    std::vector<uint8_t> result{};\n\n    if (!file.good()) {\n        return result;\n    }\n\n    file.seekg(0, std::ios::end);\n    result.resize(file.tellg());\n\n    file.seekg(0, std::ios::beg);\n    file.read(reinterpret_cast<char*>(result.data()), result.size());\n\n    return result;\n}\n\ntemplate<typename T>\nstatic T byteSwap(T value)\n{\n    if constexpr (sizeof(T) == 1)\n        return value;\n    else if constexpr (sizeof(T) == 2)\n        return static_cast<T>(__builtin_bswap16(static_cast<uint16_t>(value)));\n    else if constexpr (sizeof(T) == 4)\n        return static_cast<T>(__builtin_bswap32(static_cast<uint32_t>(value)));\n    else if constexpr (sizeof(T) == 8)\n        return static_cast<T>(__builtin_bswap64(static_cast<uint64_t>(value)));\n\n    assert(false && \"Unexpected byte size.\");\n    return value;\n}\n\ntemplate<typename T>\nstatic void byteSwapInplace(T& value)\n{\n    value = byteSwap(value);\n}\n\nstruct ReadStream\n{\n    const uint8_t* data = nullptr;\n    int size = 0; // Size from every compressed block.\n};\n\nstatic int mspackRead(mspack_file* file, void* buffer, int bytes)\n{\n    ReadStream* stream = reinterpret_cast<ReadStream*>(file);\n\n    if (stream->size == 0)\n    {\n        uint16_t size = byteSwap(*reinterpret_cast<const uint16_t*>(stream->data));\n        stream->data += sizeof(uint16_t);\n\n        // This indicates there is an uncompressed block size available. We don't need it so we skip it.\n        if ((size & 0xFF00) == 0xFF00)\n        {\n            stream->data += 1;\n            size = byteSwap(*reinterpret_cast<const uint16_t*>(stream->data));\n            stream->data += sizeof(uint16_t);\n        }\n\n        stream->size = size;\n    }\n\n    int sizeToRead = std::min(stream->size, bytes);\n\n    memcpy(buffer, stream->data, sizeToRead);\n    stream->data += sizeToRead;\n    stream->size -= sizeToRead;\n\n    return sizeToRead;\n}\n\nstruct WriteStream\n{\n    uint8_t* data = nullptr;\n    std::size_t size = 0; // Remaining available space in the stream.\n};\n\nstatic int mspackWrite(mspack_file* file, void* buffer, int bytes)\n{\n    WriteStream* stream = reinterpret_cast<WriteStream*>(file);\n\n    std::size_t sizeToWrite = std::min(stream->size, static_cast<std::size_t>(bytes));\n\n    memcpy(stream->data, buffer, sizeToWrite);\n    stream->data += sizeToWrite;\n    stream->size -= sizeToWrite;\n\n    return static_cast<int>(sizeToWrite);\n}\n\nstatic void* mspackAlloc(mspack_system* self, size_t bytes)\n{\n    return operator new(bytes);\n}\n\nstatic void mspackFree(void* ptr)\n{\n    operator delete(ptr);\n}\n\nstatic void mspackCopy(void* src, void* dst, size_t bytes)\n{\n    memcpy(dst, src, bytes);\n}\n\nstatic mspack_system g_lzxSystem =\n{\n    nullptr,\n    nullptr,\n    mspackRead,\n    mspackWrite,\n    nullptr,\n    nullptr,\n    nullptr,\n    mspackAlloc,\n    mspackFree,\n    mspackCopy\n};\n\n// Xbox Compression header definitions.\nstatic constexpr uint32_t XCompressSignature = 0xFF512EE;\n\nstruct XCompressHeader\n{\n    uint32_t signature;\n    uint32_t field04;\n    uint32_t field08;\n    uint32_t field0C;\n    uint32_t windowSize;\n    uint32_t compressedBlockSize;\n    uint64_t uncompressedSize;\n    uint64_t compressedSize;\n    uint32_t uncompressedBlockSize;\n    uint32_t field2C;\n\n    void byteSwap()\n    {\n        byteSwapInplace(signature);\n        byteSwapInplace(field04);\n        byteSwapInplace(field08);\n        byteSwapInplace(field0C);\n        byteSwapInplace(windowSize);\n        byteSwapInplace(compressedBlockSize);\n        byteSwapInplace(uncompressedSize);\n        byteSwapInplace(compressedSize);\n        byteSwapInplace(uncompressedBlockSize);\n        byteSwapInplace(field2C);\n    }\n};\n\nint main(int argc, char** argv)\n{\n    if (argc < 3)\n    {\n        printf(\"Usage: x_decompress [input file path] [output file path]\");\n        return EXIT_SUCCESS;\n    }\n\n    std::vector<uint8_t> file = readAllBytes(argv[1]);\n    if (file.empty())\n    {\n        fprintf(stderr, \"Input file \\\"%s\\\" not found or empty\", argv[1]);\n        return EXIT_FAILURE;\n    }\n\n    std::vector<uint8_t> decompressedFile;\n\n    if (file.size() >= sizeof(XCompressHeader) && byteSwap(*reinterpret_cast<uint32_t*>(file.data())) == XCompressSignature)\n    {\n        XCompressHeader* header = reinterpret_cast<XCompressHeader*>(file.data());\n        header->byteSwap();\n\n        decompressedFile.resize(header->uncompressedSize);\n\n        const uint8_t* srcBytes = file.data() + sizeof(XCompressHeader);\n\n        WriteStream dstStream;\n        dstStream.data = decompressedFile.data();\n        dstStream.size = decompressedFile.size();\n\n        // libmspack wants the bit index. This value is always guaranteed to be a power of two,\n        // so we can extract the bit index by counting the amount of leading zeroes.\n        int windowBits = 0;\n        uint32_t windowSize = header->windowSize;\n        while ((windowSize & 0x1) == 0)\n        {\n            ++windowBits;\n            windowSize >>= 1;\n        }\n\n        // Loop over compressed blocks.\n        while (srcBytes < (file.data() + file.size()) && dstStream.data < (decompressedFile.data() + decompressedFile.size()))\n        {\n            uint32_t compressedSize = byteSwap(*reinterpret_cast<const uint32_t*>(srcBytes));\n            srcBytes += sizeof(uint32_t);\n\n            ReadStream srcStream;\n            srcStream.data = srcBytes;\n\n            std::size_t uncompressedBlockSize = std::min(static_cast<std::size_t>(header->uncompressedBlockSize), dstStream.size);\n\n            lzxd_stream* lzx = lzxd_init(\n                &g_lzxSystem,\n                reinterpret_cast<mspack_file*>(&srcStream),\n                reinterpret_cast<mspack_file*>(&dstStream),\n                windowBits,\n                0,\n                static_cast<int>(header->compressedBlockSize),\n                static_cast<off_t>(uncompressedBlockSize),\n                0);\n\n            lzxd_decompress(lzx, uncompressedBlockSize);\n            lzxd_free(lzx);\n\n            srcBytes += compressedSize;\n        }\n    }\n    else\n    {\n        decompressedFile = std::move(file);\n    }\n\n    std::ofstream outputFile(argv[2], std::ios::binary);\n    if (!outputFile.good())\n    {\n        fprintf(stderr, \"Cannot open output file \\\"%s\\\" for writing\", argv[2]);\n        return EXIT_FAILURE;\n    }\n\n    outputFile.write(reinterpret_cast<const char*>(decompressedFile.data()), decompressedFile.size());\n\n    return EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "update_submodules.bat",
    "content": "git submodule update --init --recursive"
  },
  {
    "path": "vcpkg.json",
    "content": "{\n    \"builtin-baseline\": \"b322364f06308bdd24823f9d8f03fe0cc86fd46f\",\n    \"dependencies\": [\n        {\n            \"name\": \"directx-headers\",\n            \"platform\": \"windows\"\n        },\n        {\n            \"name\": \"directx12-agility\",\n            \"platform\": \"windows\"\n        },\n        \"freetype\",\n        \"curl\"\n    ],\n    \"vcpkg-configuration\": {\n        \"overlay-triplets\": [\n            \"vcpkg/triplets\"\n        ]\n    }\n}\n"
  }
]